日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

链表(单链表、双链表、内核链表)

發布時間:2023/12/20 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 链表(单链表、双链表、内核链表) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

以下內容源于朱有鵬嵌入式課程的學習,如有侵權,請告知刪除。


一、鏈表的引入

1、從數組的缺陷說起

  • 數組有2個缺陷,一個是數組中所有元素的類型必須一致;第二個是數組的元素個數必須事先制定并且一旦指定之后不能更改。
  • 數組的第一個缺陷靠結構體去解決。結構體允許其中的元素的類型不相同,因此解決了數組的第一個缺陷。所以說結構體是因為數組不能解決某些問題所以才發明的。
  • 數組的第二個缺陷靠鏈表去解決。

2、鏈表

  • 鏈表是由若干個節點組成的(鏈表的各個節點結構是完全類似的),節點是由有效數據和指針組成的。
  • 有效數據區域用來存儲信息完成任務的,指針區域用于指向鏈表的下一個節點從而構成鏈表。

二、單鏈表

1、頭指針

  • 頭指針并不是節點,而是一個普通指針,只占4字節。頭指針的類型是struct node *類型的,所以它才能指向鏈表的節點。
  • 一個典型的鏈表的實現就是:頭指針指向鏈表的第1個節點,然后第1個節點中的指針指向下一個節點,然后依次類推一直到最后一個節點。

2、頭結點

(1)問題

  • 如果程序中直接定義了頭指針后就直接insert_tail就會報段錯誤。我們不得不在定義頭指針之后先創建一個新節點給頭指針初始化,否則不能避免這個錯誤;但是這樣解決讓程序看起來邏輯有點不太順,因為看起來第一個節點和后面的節點的創建、添加方式有點不同。

(2)頭結點特點

  • 把頭指針指向的第一個節點作為頭節點使用。
  • 頭節點的特點是:緊跟在頭指針后面,頭節點的數據部分是空的(有時候存儲整個鏈表的節點數),頭結點的指針部分指向下一個節點,即第一個節點。
  • 在創建頭指針時一并創建頭結點,并且和頭指針關聯起來。
(3)有無頭結點區別

  • 鏈表有沒有頭節點是不同的,體現在鏈表的插入節點、刪除節點、遍歷節點、解析鏈表的各個算法函數都不同。
  • 所以如果一個鏈表設計的時候就有頭節點那么后面的所有算法都應該這樣來處理;如果設計時就沒有頭節點,那么后面的所有算法都應該按照沒有頭節點來做。
  • 實際編程中兩種鏈表都有人用,所以大家在看別人寫的代碼時一定要注意看它有沒有頭節點。

2、插入節點

參考博客:http://blog.csdn.net/oqqhutu12345678/article/details/52628769(無頭結點)

(1)從鏈表頭部插入新節點

(3)從鏈表尾部插入新節點

  • 尾部插入簡單點,因為前面已經建立好的鏈表不用動,直接動最后一個就可以。

(3)鏈表即可以從頭部插入,也可以從尾部插入,還可以兩頭插入。

  • 對鏈表本身無差別,但是有時候對業務邏輯有差別。

3、遍歷節點

4、刪除節點

(1)刪除節點的2個步驟

  • 第一步:找到要刪除的節點;第二步:刪除這個節點。

(2)如何刪除一個節點

  • 待刪除的節點是否尾節點

5、逆序

(1)什么是鏈表的逆序?

  • 鏈表的逆序又叫反向,意思就是把鏈表中所有的有效節點在鏈表中的順序給反過來。

(2)單鏈表逆序算法分析

  • 首先遍歷原鏈表,然后將原鏈表中的頭指針和頭節點作為新鏈表的頭指針和頭節點,原鏈表中的有效節點挨個依次取出來,采用頭插入的方法插入新鏈表中即可。
  • 鏈表逆序 = 遍歷 + 頭插入。

#include <stdio.h> #include <strings.h> #include <stdlib.h>struct node {int data; 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;}// 清理申請到的堆內存bzero(p, sizeof(struct node));// 填充節點p->data = data;p->pNext = NULL; return p; }void insert_tail(struct node *pH, struct node *new) {int cnt = 0;// 分兩步來完成插入// 第一步,先找到鏈表中最后一個節點struct node *p = pH;while (NULL != p->pNext){p = p->pNext; // 往后走一個節點cnt++;}// 第二步,將新節點插入到最后一個節點尾部p->pNext = new;pH->data = cnt + 1; }void insert_head(struct node *pH, struct node *new)//第一個參數是頭指針,第二個參數是指向新建節點的指針 {// 第1步: 新節點的next指向原來的第一個節點new->pNext = pH->pNext;//????是因為創建了一個頭結點,我們的插入,是在頭結點和第一個節點間插入//new->pNext = pH;這才對吧?待驗證。無頭結點時的用法// 第2步: 頭節點的next指向新節點的地址pH->pNext = new;// 第3步: 頭節點中的計數要加1pH->data += 1; }// 遍歷單鏈表,pH為指向單鏈表的頭指針,遍歷的節點數據打印出來 void bianli(struct node*pH) {struct node *p = pH->pNext; // p直接走到第一個節點printf("-----------開始遍歷-----------\n");while (NULL != p->pNext) // 是不是最后一個節點{printf("node data: %d.\n", p->data);p = p->pNext; // 走到下一個節點,也就是循環增量}printf("node data: %d.\n", p->data);//這打印最后一個節點的數據。破壞了統一性。printf("-------------完了-------------\n"); }void bianli2(struct node*pH) {struct node *p = pH; // 頭指針后面是頭節點printf("-----------開始遍歷-----------\n");while (NULL != p->pNext) // 是不是最后一個節點{p = p->pNext; // 走到下一個節點,也就是循環增量printf("node data: %d.\n", p->data);}printf("-------------完了-------------\n"); }// 從鏈表pH中刪除節點,待刪除的節點的特征是數據區等于data // 返回值:當找到并且成功刪除了節點則返回0,當未找到節點時返回-1 int delete_node(struct node*pH, int data) {// 找到這個待刪除的節點,通過遍歷鏈表來查找struct node *p = pH; // 用來指向當前節點struct node *pPrev = NULL; // 用來指向當前節點的前一個節點while (NULL != p->pNext) // 是不是最后一個節點{pPrev = p; // 在p走向下一個節點前先將其保存p = p->pNext; // 走到下一個節點,也就是循環增量// 判斷這個節點是不是我們要找的那個節點if (p->data == data){// 找到了節點,處理這個節點// 分為2種情況,一個是找到的是普通節點,另一個是找到的是尾節點// 刪除節點的困難點在于:通過鏈表的遍歷依次訪問各個節點,找到這個節點// 后p指向了這個節點,但是要刪除這個節點關鍵要操作前一個節點,但是這// 時候已經沒有指針指向前一個節點了,所以沒法操作。解決方案就是增加// 一個指針指向當前節點的前一個節點if (NULL == p->pNext){// 尾節點pPrev->pNext = NULL; // 原來尾節點的前一個節點變成新尾節點free(p); // 釋放原來的尾節點的內存}else{// 普通節點pPrev->pNext = p->pNext; // 要刪除的節點的前一個節點和它的后一個節點相連,這樣就把要刪除的節點給摘出來了free(p);}// 處理完成之后退出程序return 0;}}// 到這里還沒找到,說明鏈表中沒有我們想要的節點printf("沒找到這個節點.\n");return -1; }// 將pH指向的鏈表逆序 void reverse_linkedlist(struct node *pH) {struct node *p = pH->pNext; // pH指向頭節點,p指向第1個有效節點struct node *pBack; // 保存當前節點的后一個節點地址// 當鏈表沒有有效節點或者只有一個有效節點時,逆序不用做任何操作if ((NULL ==p) || (NULL == p->pNext))return;// 當鏈表有2個及2個以上節點時才需要真正進行逆序操作while (NULL != p->pNext) // 是不是最后一個節點{// 原鏈表中第一個有效節點將是逆序后新鏈表的尾節點,尾節點的pNext指向NULLpBack = p->pNext; // 保存p節點后面一個節點地址if (p == pH->pNext){// 原鏈表第一個有效節點p->pNext = NULL;}else{// 原鏈表的非第1個有效節點p->pNext = pH->pNext;}pH->pNext = p;//p = p->pNext; // 這樣已經不行了,因為p->pNext已經被改過了p = pBack; // 走到下一個節點}// 循環結束后,最后一個節點仍然缺失insert_head(pH, p); }int main(void) {// 定義頭指針,創建頭結點,并把頭指針指向頭結點struct node *pHeader = create_node(0);//創建頭結點insert_head(pHeader, create_node(11));//11,12,13是所創建的節點數據,這里采用頭插法。insert_head(pHeader, create_node(12));insert_head(pHeader, create_node(13));bianli2(pHeader);return 0; }


三、雙鏈表

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





總結

以上是生活随笔為你收集整理的链表(单链表、双链表、内核链表)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。