链表(单链表、双链表、内核链表)
生活随笔
收集整理的這篇文章主要介紹了
链表(单链表、双链表、内核链表)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
以下內容源于朱有鵬嵌入式課程的學習,如有侵權,請告知刪除。
一、鏈表的引入
1、從數組的缺陷說起
- 數組有2個缺陷,一個是數組中所有元素的類型必須一致;第二個是數組的元素個數必須事先制定并且一旦指定之后不能更改。
- 數組的第一個缺陷靠結構體去解決。結構體允許其中的元素的類型不相同,因此解決了數組的第一個缺陷。所以說結構體是因為數組不能解決某些問題所以才發明的。
- 數組的第二個缺陷靠鏈表去解決。
2、鏈表
- 鏈表是由若干個節點組成的(鏈表的各個節點結構是完全類似的),節點是由有效數據和指針組成的。
- 有效數據區域用來存儲信息完成任務的,指針區域用于指向鏈表的下一個節點從而構成鏈表。
二、單鏈表
1、頭指針
- 頭指針并不是節點,而是一個普通指針,只占4字節。頭指針的類型是struct node *類型的,所以它才能指向鏈表的節點。
- 一個典型的鏈表的實現就是:頭指針指向鏈表的第1個節點,然后第1個節點中的指針指向下一個節點,然后依次類推一直到最后一個節點。
2、頭結點
(1)問題
- 如果程序中直接定義了頭指針后就直接insert_tail就會報段錯誤。我們不得不在定義頭指針之后先創建一個新節點給頭指針初始化,否則不能避免這個錯誤;但是這樣解決讓程序看起來邏輯有點不太順,因為看起來第一個節點和后面的節點的創建、添加方式有點不同。
(2)頭結點特點
- 把頭指針指向的第一個節點作為頭節點使用。
- 頭節點的特點是:緊跟在頭指針后面,頭節點的數據部分是空的(有時候存儲整個鏈表的節點數),頭結點的指針部分指向下一個節點,即第一個節點。
- 在創建頭指針時一并創建頭結點,并且和頭指針關聯起來。
- 鏈表有沒有頭節點是不同的,體現在鏈表的插入節點、刪除節點、遍歷節點、解析鏈表的各個算法函數都不同。
- 所以如果一個鏈表設計的時候就有頭節點那么后面的所有算法都應該這樣來處理;如果設計時就沒有頭節點,那么后面的所有算法都應該按照沒有頭節點來做。
- 實際編程中兩種鏈表都有人用,所以大家在看別人寫的代碼時一定要注意看它有沒有頭節點。
2、插入節點
參考博客:http://blog.csdn.net/oqqhutu12345678/article/details/52628769(無頭結點)
(1)從鏈表頭部插入新節點
(3)從鏈表尾部插入新節點
- 尾部插入簡單點,因為前面已經建立好的鏈表不用動,直接動最后一個就可以。
(3)鏈表即可以從頭部插入,也可以從尾部插入,還可以兩頭插入。
- 對鏈表本身無差別,但是有時候對業務邏輯有差別。
3、遍歷節點
4、刪除節點
(1)刪除節點的2個步驟
- 第一步:找到要刪除的節點;第二步:刪除這個節點。
(2)如何刪除一個節點
- 待刪除的節點是否尾節點
5、逆序
(1)什么是鏈表的逆序?
- 鏈表的逆序又叫反向,意思就是把鏈表中所有的有效節點在鏈表中的順序給反過來。
(2)單鏈表逆序算法分析
- 首先遍歷原鏈表,然后將原鏈表中的頭指針和頭節點作為新鏈表的頭指針和頭節點,原鏈表中的有效節點挨個依次取出來,采用頭插入的方法插入新鏈表中即可。
- 鏈表逆序 = 遍歷 + 頭插入。
三、雙鏈表
1、單鏈表的局限性
- 局限性主要體現在單鏈表只能經由指針單向移動。
- 一旦指針移動過某個節點就無法再回來,如果要再次操作這個節點除非從頭指針開始再次遍歷一次。
- 因此單鏈表的某些操作就比較麻煩(算法比較有局限)。
2、雙鏈表
- 雙向鏈表的節點 = 有效數據 + 2個指針(一個指向后一個節點,另一個指向前一個節點)。
3、插入節點
(1)尾部插入
(2)頭部插入
4、遍歷節點
- 雙鏈表中如果無視pPrev指針,則雙鏈表就變成了單鏈表。這就決定了雙鏈表的正向遍歷(后向遍歷)和單鏈表是完全相同的。
- 前向遍歷的意義并不大,主要是因為很少有當前當了尾節點需要前向遍歷的情況。
- 雙鏈表是對單鏈表的一種有成本的擴展,因此在實踐用途中要根據業務要求選擇適合的鏈表。
5、刪除節點
#include <stdio.h> #include <stdlib.h>// 雙鏈表的節點 struct node {int data; // 有效數據struct node *pPrev; // 前向指針,指向前一個節點struct node *pNext; // 后向指針,指向后一個節點 };struct node *create_node(int data) {struct node *p = (struct node *)malloc(sizeof(struct node));if (NULL == p){printf("malloc error.\n");return NULL;}p->data = data;p->pPrev = NULL;p->pNext = NULL; // 默認創建的節點前向后向指針都指向NULLreturn p; }// 將新節點new插入到鏈表pH的尾部 void insert_tail(struct node *pH, struct node *new) {// 第一步先走到鏈表的尾節點struct node *p = pH;while (NULL != p->pNext){p = p->pNext; // 第一次循環走過了頭節點}// 循環結束后p就指向了原來的最后一個節點// 第二步:將新節點插入到原來的尾節點的后面p->pNext = new; // 后向指針關聯好了。新節點的地址和前節點的nextnew->pPrev = p; // 前向指針關聯好了。新節點的prev和前節點的地址// 前節點的prev和新節點的next指針未變動 }// 將新節點new前插入鏈表pH中。 // 算法參照圖示進行連接,一共有4個指針需要賦值。注意的是順序。 void insert_head(struct node *pH, struct node *new) {// 新節點的next指針指向原來的第1個有效節點的地址new->pNext = pH->pNext;// 原來第1個有效節點的prev指針指向新節點的地址if (NULL != pH->pNext)pH->pNext->pPrev = new;// 頭節點的next指針指向新節點地址pH->pNext = new;// 新節點的prev指針指向頭節點的地址new->pPrev = pH; }// 后向遍歷一個雙鏈表 void bianli(struct node *pH) {struct node *p = pH;if (NULL == p){return;}while (NULL != p->pNext){p = p->pNext;printf("data = %d.\n", p->data);} }// 前向遍歷一個雙遍歷,參數pTail要指向鏈表末尾 void qianxiang_bianli(struct node *pTail) {struct node *p = pTail;while (NULL != p->pPrev){printf("data = %d.\n", p->data);p = p->pPrev;} }// 從鏈表pH中刪除一個節點,節點中的數據是data int delete_node(struct node *pH, int data) {struct node *p = pH;if (NULL == p){return -1;}while (NULL != p->pNext){p = p->pNext;// 在這里先判斷當前節點是不是我們要刪除的那個節點if (p->data == data){// 找到了,刪除之。當前上下文是:當前節點為pif (NULL == p->pNext){// 尾節點 // p表示當前節點地址,p->pNext表示后一個節點地址,p->pPrev表示前一個節點的地址p->pPrev->pNext = NULL;//p->pPrev = NULL; 可以省略,因為后面整個都被銷毀了// 銷毀p節點//free(p);}else{// 不是尾節點,普通節點// 前一個節點的next指針指向后一個節點的首地址p->pPrev->pNext = p->pNext;// 當前節點的prev和next指針都不用管,因為后面會整體銷毀整個節點// 后一個節點的prev指針指向前一個節點的首地址p->pNext->pPrev = p->pPrev;//free(p);}free(p);return 0;}}printf("未找到目標節點.\n");return -1; }int main(void) {//struct node *pHeader = create_node(0); // 頭指針struct node *pHeader = NULL;//insert_head(pHeader, create_node(11));//insert_head(pHeader, create_node(12));//insert_head(pHeader, create_node(13));/*// 遍歷printf("node 1 data: %d.\n", pHeader->pNext->data);printf("node 2 data: %d.\n", pHeader->pNext->pNext->data);printf("node 3 data: %d.\n", pHeader->pNext->pNext->pNext->data);struct node *p = pHeader->pNext->pNext->pNext; // p指向了最后一個節點printf("node 3 data: %d.\n", p->data);printf("node 2 data: %d.\n", p->pPrev->data);printf("node 1 data: %d.\n", p->pPrev->pPrev->data);*///bianli(pHeader);//struct node *p = pHeader->pNext->pNext->pNext;//qianxiang_bianli(p);bianli(pHeader);delete_node(pHeader, 11);printf("after delete node------------------\n");bianli(pHeader);return 0; }四、linux內核鏈表
見博客http://blog.csdn.net/oqqhutu12345678/article/details/78521640
總結
以上是生活随笔為你收集整理的链表(单链表、双链表、内核链表)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: afn post请求上传文件_iOS利用
- 下一篇: anaconda下载与spyder的报错