[C語言] 單向鏈表的構建以及翻轉算法_圖文詳解(附雙向鏈表構建代碼)

  [C語言]單向鏈表的構建以及翻轉算法
node

 

1、基本概念

  單向鏈表的連接方向是單向的,其中每一個結點都有指針成員變量指向鏈表中的下一個結點,訪問鏈表時要從頭節點(帶頭節點的鏈表)或存儲首個數據的節點(不帶頭節點的鏈表)開始順序查詢。本文將以帶頭結點的非循環單向鏈表爲例,其鏈表模型以下:算法

  

  其中head爲頭結點(不存儲數據)、data節點存儲數據、pNext存儲下一節點的地址。
ide

  當單項鍊表不包含頭結點時,鏈表首個節點即是所存儲的第一個數據的節點;當單項鍊表是循環鏈表時,鏈表中存儲最後一個數據的節點中pNext指向的不是NULL而是鏈表首部。
spa

 

 

2、構建算法

一、單項鍊表節點結構體的定義3d

  鏈表節點包含兩個部分:①節點所存儲的數據。②節點所存儲的下一節點地址。指針

  其單個節點模型以下:code

  

1 typedef int T;
2 
3 typedef struct SLNode
4 {
5     //節點保存的數據
6     T data;
7     //下一個節點的位置
8     struct SLNode* next;
9 }SLNode,SLLink;
 1 typedef int T;
 2 
 3 typedef struct DLNode
 4 {
 5     //節點保存的數據
 6     T data;
 7     struct DLNode* prev;
 8     struct DLNode* next;
 9 }DLNode;
10 
11 typedef struct DLLink
12 {
13     struct DLNode* head;
14     struct DLNode* tail;
15 }DLLink;
View Code(雙向鏈表)

  tips:①用 typedef 定義數據類型能有效提升代碼的實用性,②通常用 NODE* 定義節點, LINK* 定義鏈表,便以區分。blog

 

二、 單項鍊表的始終ip

2.1 單項鍊表的建立內存

  單項鍊表的建立主要包含三個部分:①爲申請頭結點內存,②使pNext指向下一節點(NULL)。③返回頭地址。

  其頭節點模型以下:

  

 1 //建立一個鏈表
 2 SLLink* creat_SLLink()
 3 {
 4     //建立一個節點,表示頭節點,該節點並不保存數據
 5     SLLink* head = (SLLink*)malloc(sizeof(SLNode));
 6     //讓頭節點的next置NULL,表示鏈表爲空
 7     head->next = NULL;
 8     //返回頭的地址
 9     return head;
10 }
 1 //建立一個節點
 2 DLNode* creat_DLNode()
 3 {
 4     //建立一個節點,該節點並不保存數據且先後置空
 5     DLNode* node = (DLNode*)malloc(sizeof(DLNode));
 6     node->prev = NULL;
 7     node->next = NULL;
 8     //返回節點的地址
 9     return node;
10 }
11 
12 //建立一個鏈表
13 DLLink* creat_DLLink()
14 {
15     //建立鏈表與頭尾節點,頭尾節點並不保存數據
16     DLLink* link = (DLLink*)malloc(sizeof(DLLink));
17     link->head = creat_DLNode();
18     link->tail = creat_DLNode();
19     //讓頭尾節點的互指
20     link->head->next = link->tail;
21     link->head->prev = NULL;
22     link->tail->next = NULL;
23     link->tail->prev = link->head;
24     //返回鏈表地址
25     return link;
26 }
View Code(雙向鏈表)

 

2.2 單項鍊表的清空與銷燬

  單項鍊表的清空與銷燬主要包含兩個步驟:①依次釋放存儲數據的節點內存。②釋放頭結點的內存,變量置空。

 1 //清空鏈表
 2 void clear_SLLink(SLLink* link)
 3 {
 4     SLNode* node = link->next;
 5     while(node != NULL)
 6     {
 7         SLNode* tmp = node;
 8         node = node->next;
 9         free(tmp);
10     }
11     link->next = NULL;
12 }
13 
14 //銷燬鏈表
15 void destroy_SLLink(SLLink* link)
16 {
17     clear_SLLink(link);
18     free(link);
19     link = NULL;
20 }
 1 //清空鏈表
 2 void clear_DLLink(DLLink* link)
 3 {
 4     DLNode* node = link->head->next;
 5     while(node != link->tail)
 6     {
 7         node = node->next;
 8         free(node->prev);
 9     }
10     //讓頭尾節點的互指
11     link->head->next = link->tail;
12     link->tail->prev = link->head;
13 }
14 
15 //銷燬鏈表
16 void destroy_DLLink(DLLink* link)
17 {
18     clear_DLLink(link);
19     free(link);
20     link = NULL;
21 }
View Code(雙向鏈表)

 

 

三、 單項鍊表的斷定

3.1 單項斷定鏈表是否爲空

1 //判斷鏈表是否爲空
2 bool emtpy_SLLink(SLLink* link)
3 {
4     return !link->next;
5 }
1 //判斷鏈表是否爲空
2 bool emtpy_DLLink(DLLink* link)
3 {
4     return link->tail->next == link->tail;
5 }
View Code(雙向鏈表)

 

四、單向鏈表的查詢

4.1 獲取單向鏈表中數據的個數

  獲取單向鏈表中數據的個數主要包含三個步驟:①建立變量存儲數據個數、建立node記錄頭節點的下一節位置點用於遍歷鏈表。②循環遍歷,噹噹前節點的pNext指向NULL時表明已達鏈表末尾,結束循環。③返回數據個數。

 1 //得到鏈表裏數據的個數
 2 size_t size_SLLink(SLLink* link)
 3 {    
 4     size_t i = 0;
 5     //用node記錄頭節點的下一個位置
 6     SLNode* node = link->next;
 7     //只要node不爲NULL,表示該節點存在
 8     while(node != NULL)
 9     {
10         //讓node繼續指向下一個節點
11         node = node->next;
12         i++;
13     }
14     return i;
15 }
 1 //得到鏈表裏元素的個數
 2 size_t size_DLLink(DLLink* link)
 3 {    
 4     size_t i = 0;
 5     //用node記錄頭節點的下一個位置
 6     DLNode* node = link->head->next;
 7     //只要node不爲NULL,表示該節點存在
 8     while(node != link->tail)
 9     {
10         //讓node繼續指向下一個節點
11         node = node->next;
12         i++;
13     }
14     return i;
15 }
View Code(雙向鏈表)

 

4.2 獲取指定下標節點的前一個節點

  獲取指定下標節點的前一個節點主要包含三個步驟:①建立node記錄頭節點位置用於遍歷鏈表。②循環遍歷,噹噹前節點的pNext指向NULL時表明已達鏈表末尾,結束循環。③返回指定下標的前一個節點。

 1 //返回下標爲index的前一個節點
 2 SLNode* getNode_SLLink(SLLink* link, int index)
 3 {
 4     SLNode* node = link;
 5     //若是index=0 其前一個節點就爲link ,並不會進入下面循環
 6     for(int i=0; node != NULL && i<index; i++)
 7     {
 8         node = node->next;
 9     }
10     return node;
11 }
 1 //返回下標爲index的個節點
 2 DLNode* getNode_DLLink(DLLink* link, int index)
 3 {
 4     DLNode* node = link->head->next;
 5     //若是index=0 其前一個節點就爲DLLink ,並不會進入下面循環
 6     for(int i=0; node != link->tail && i<index; i++)
 7     {
 8         node = node->next;
 9     }
10     return node;
11 }
View Code(雙向鏈表)

 

4.3 查找指定數據的下標(第一個)

  查找指定數據的下標主要包含主要包含三個步驟:①建立node記錄頭節點的下一節位置點用於遍歷鏈表。②循環遍歷,噹噹前節點的pNext指向NULL時表明已達鏈表末尾,結束循環。③成功返回數據下標,失敗則返回 -1 。

 1 //查找數據value的下標
 2 int indexOf_SLLink(SLLink* link, T value)
 3 {
 4     SLNode* node = link->next;
 5     for(int i = 0; node != NULL; i++)
 6     {
 7         if(node->data == value)
 8             return i;
 9         node = node->next;
10     }
11     return -1;
12 }
 1 //查找元素value的下標
 2 int indexOf_DLLink(DLLink* link, T value)
 3 {
 4     DLNode* node = link->head->next;
 5     for(int i = 0; node != link->tail; i++)
 6     {
 7         if(node->data == value)
 8         {
 9             return i;
10         }
11         node = node->next;
12     }
13     return -1;
14 }
View Code(雙向鏈表)

 

4.4 遍歷顯示鏈表

  遍歷顯示鏈表主要包含主要包含三個步驟:①建立node記錄頭節點的下一節位置點用於遍歷鏈表。②循環遍歷,順序輸出每個節點中儲存的數據,噹噹前節點的pNext指向NULL時表明已達鏈表末尾,結束循環。

 1 //遍歷鏈表
 2 void travel_SLLink(SLLink* link)
 3 {
 4     SLNode* node = link->next;
 5     while(node != NULL)
 6     {
 7         printf("%d ",node->data);
 8         node = node->next;
 9     }
10     puts("");
11 }
 1 //順序遍歷鏈表
 2 void sequenceTravel_DLLink(DLLink* link)
 3 {
 4     DLNode* node = link->head->next;
 5     while(node != link->tail)
 6     {
 7         printf("%d ",node->data);
 8         node = node->next;
 9     }
10     puts("");
11 }
View Code(雙向鏈表)

 

五、 單項鍊表的節點插入

5.1 將一個節點插入指定下標

  將一個節點插入指定下標(index)主要包含三個步驟:①獲取指定下標節點的上一個節點位置。②建立node節點存儲須要插入的數據。③讓插入節點的下一個節點指向index前一個節點的後節點,讓index的前節點的下一個節點指向當前插入的節點。

  其演示模型以下:

 1 //插入一個元素到指定位置 index取值範圍[0,size_SLLink(SLLink* link)]
 2 bool insert_SLLink(SLLink* link, int index, T value)
 3 {
 4     if(index < 0 || index > size_SLLink(link)) 
 5         return false;
 6     //獲得下標爲index位置的前一個節點
 7     SLNode* prevSLNode = getNode_SLLink(link, index);
 8     //申請內存,用於保存須要插入的數據
 9     SLNode* node = (SLNode*)malloc(sizeof(SLNode));
10     node->data = value;
11     //插入節點的下一個節點指向index前一個節點的後節點
12     node->next = prevSLNode->next;
13     //讓index的前節點的下一個節點指向當前插入的節點
14     prevSLNode->next = node;
15     return true;
16 }
17 //插入一個元素到鏈表末尾
18 bool insertBack_SLLink(SLLink* link, T value)
19 {
20     return insert_SLLink(link, size_SLLink(link), value);
21 }
22 //插入一個元素到鏈表首部
23 bool insertFront_SLLink(SLLink* link, T value)
24 {
25     return insert_SLLink(link, 0, value);
26 }
 1 //插入一個元素到指定位置 index取值範圍[0,size_DLLink(DLLink* link)
 2 bool insert_DLLink(DLLink* link, int index, T value)
 3 {
 4     if(index < 0 || index > size_DLLink(link)) 
 5     {
 6         return false;
 7     }
 8     //獲得下標爲index節點
 9     DLNode* currNode = getNode_DLLink(link, index);
10     //申請內存,用於保存須要插入的數據
11     DLNode* insertNode = (DLNode*)malloc(sizeof(DLNode));
12     insertNode->data = value;
13     //插入節點的下一個節點指向index當前節點
14     insertNode->next = currNode;
15     //插入節點的前一個節點指向index節點的前一個節點
16     insertNode->prev = currNode->prev;
17     //讓index當前的前節點的下一個節點指向插入的節點
18     currNode->prev->next = insertNode;
19     //讓index當前節點的上一個節點指向插入的節點
20     currNode->prev = insertNode;
21     return true;
22 }
23 //插入一個元素到鏈表末尾
24 bool insertBack_DLLink(DLLink* link, T value)
25 {
26     return insert_DLLink(link, size_DLLink(link)-1, value);
27 }
28 //插入一個元素到鏈表首部
29 bool insertFront_DLLink(DLLink* link, T value)
30 {
31     return insert_DLLink(link, 0, value);
32 }
View Code(雙向鏈表)

 

5.2 將一個節點替換指定下標節點

  將一個節點替換指定下標(index)節點只包含一個步驟:建立node節點獲取指定下標節點的位置並存儲須要插入的數據。

1 //更新鏈表下標爲index的節點的值
2 bool update_SLink(SLLink* link, int index, T value)
3 {
4     if(index < 0 || index > size_link(link)-1)
5         return false;
6     SLNode* node = getNode_SLLink(link, index+1);
7     node->data = value;
8     return true;
9 }
 1 //更新鏈表下標爲index的節點的值
 2 bool update_DLLink(DLLink* link, int index, T value)
 3 {
 4     if(index < 0 || index > size_DLLink(link)-1)
 5     {
 6         return false;
 7     }
 8     DLNode* node = getNode_DLLink(link, index);
 9     node->data = value;
10     return true;
11 }
View Code(雙向鏈表)

 

六、單項鍊表的數據刪除

6.1 將一個指定下標的節點刪除

  將一個指定下標的節點(index)刪除主要包含四個步驟:①獲取指定下標節點的上一個節點位置。②保存要刪除的節點,用於釋放內存。③讓要刪除節點的前一個節點指向要刪除節點的後一個節點。④釋放內存。

  其演示模型以下:

 1 //刪除指定下標的元素
 2 bool delete_SLLink(SLLink* link, int index)
 3 {
 4     if(index < 0 || index > size_SLLink(link)-1) 
 5         return false;
 6     //得到須要刪除節點的前一個節點
 7     SLNode* prevSLNode = getNode_SLLink(link, index);
 8     //保存要刪除的節點,用於釋放內存
 9     SLNode* node  = prevSLNode->next;
10     //讓要刪除節點的前一個節點指向要刪除節點的後一個節點
11     prevSLNode->next = prevSLNode->next->next;
12     free(node);
13     return true;
14 }
 1 //刪除指定下標的元素
 2 bool delete_DLLink(DLLink* link, int index)
 3 {
 4     if(index < 0 || index > size_DLLink(link)-1) 
 5     {
 6         return false;
 7     }
 8     //得到須要刪除節點
 9     DLNode* currNode = getNode_DLLink(link, index+1);
10     //保存要刪除的節點,用於釋放內存
11     DLNode* tmp = currNode;
12     currNode->prev->next = currNode->next;
13     currNode->next->prev = currNode->prev;
14     free(tmp);
15     return true;
16 }
View Code(雙向鏈表)

 

6.2 刪除鏈表中全部包含指定數據的節點

  刪除鏈表中全部包含指定數據(value)的節點主要包含三個步驟:①設置標誌變量(flag)表示該鏈表的數據是否發生變化。②循環遍歷,順序刪除每個包含指定數據的節點並將標誌變量置位置爲1,噹噹前節點的pNext指向NULL時表明已達鏈表末尾,結束循環。③返回標誌變量。

 1 //刪除元素爲value的全部節點,返回值表示該鏈表的數據是否發生變化
 2 bool deleteDatas_SLLink(SLLink* link, T value)
 3 {
 4     //做爲是否刪除成功的一個標誌
 5     bool flag = false;
 6     SLNode* prevNode = link;
 7     SLNode* currNode = link->next;
 8     while(currNode != NULL)
 9     {
10         if(currNode->data == value)
11         {
12             SLNode* tmp = currNode;
13             prevNode->next = currNode->next;
14             currNode = currNode->next;
15             free(tmp);
16             flag = true;
17         }
18         else
19         {
20             prevNode = prevNode->next;
21             currNode = currNode->next;
22         }
23     }
24     return flag;
25 }
26 
27 //刪除元素爲value的第一個元素
28 bool deleteData_SLLink(SLLink* link, T value)
29 {
30     SLNode* prevNode = link;
31     SLNode* currNode = link->next;
32     while(currNode != NULL)
33     {
34         if(currNode->data == value)
35         {
36             SLNode* tmp = currNode;
37             prevNode->next = currNode->next;
38             currNode = currNode->next;
39             free(tmp);
40             return true;
41         }
42         else
43         {
44             prevNode = prevNode->next;
45             currNode = currNode->next;
46         }
47     }
48     return false;
49 }
 1 //刪除元素爲value的全部節點,返回值表示該鏈表的數據是否發生變化
 2 bool deleteDatas_DLLink(DLLink* link, T value)
 3 {
 4     //做爲是否刪除成功的一個標誌
 5     bool flag = false;
 6     DLNode* node = link->head->next;
 7     while(node != link->tail)
 8     {
 9         if(node->data == value)
10         {
11             DLNode* tmp = node;
12             node->prev->next = node->next;
13             node->next->prev = node->prev;
14             node = node->next;
15             free(tmp);
16             flag = true;
17         }
18         else
19         {
20             node = node->next;
21         }
22     }
23     return flag;
24 }
25 
26 //刪除元素爲value的第一個元素
27 bool deleteData_DLLink(DLLink* link, T value)
28 {
29     DLNode* node = link->head->next;
30     while(node != link->tail)
31     {
32         if(node->data == value)
33         {
34             DLNode* tmp = node;
35             node->prev->next = node->next;
36             node->next->prev = node->prev;
37             free(tmp);
38             return true;
39         }
40         else
41         {
42             node = node->next;
43         }
44     }
45     return false;
46 }
View Code(雙向鏈表)

 

 

3、拓展應用

一、單項鍊表的總體翻轉

  單向鏈表的總體翻轉具體步驟見「代碼註釋」,其演示模型以下(建議先了解代碼):

  0:記錄前一個節點與當前節點

     

  1.1:先記錄當前節點的後一個節點

 

  

  2.1:讓當前節點(node)的下一個節點(node->next)指向前一個節點(prev)

 

  

  3.1:讓前一個節點指向當前節點、當前節點指向原先記錄的下一個節點

 

  

  1.2:先記錄當前節點的後一個節點

 

  

  2.2:讓當前節點(node)的下一個節點(node->next)指向前一個節點(prev)

 

  

  3.2 : 讓前一個節點指向當前節點、當前節點指向原先記錄的下一個節點

 

  

  4 : 讓原來的第一個元素變爲尾元素,尾元素的下一個置NULL

 

    

  5 : 讓鏈表的頭節點指向原來的尾元素

 

    

 1 //鏈表逆序
 2 void reverse(Link link)
 3 {
 4     if(link == NULL || link->next == NULL)
 5         return;
 6     //0、記錄前一個節點與當前節點
 7     Node* prevNode = link->next;
 8     Node* node = prevNode->next;//NULL
 9     //只要當前節點存在
10     while(node != NULL)
11     {
12         //一、先記錄當前節點的後一個節點
13         Node* nextNode = node->next;
14         //二、讓當前節點(node)的下一個節點(node->next)指向(=)前一個節點(prev)
15         node->next = prevNode;
16         //三、讓前一個節點指向當前節點、當前節點指向原先記錄的下一個節點
17         prevNode = node;
18         node = nextNode;
19     }
20     //四、讓原來的第一個元素變爲尾元素,尾元素的下一個置NULL
21     link->next->next = NULL;
22     //五、讓鏈表的頭節點指向原來的尾元素
23     link->next = prevNode;
24 }

 

二、鏈表中每k個節點進行翻轉,若最後一組節點數量不足k個,則按實際個數翻轉。

  具體流程參考「單項鍊表的總體翻轉」。

 1 //鏈表中每k個節點進行翻轉,若最後一組節點數量不足k個,則按實際個數翻轉。
 2 void reverseByNum(Node* prev,Node* node,int num)
 3 {
 4     if(node == NULL)
 5         return;
 6     Node* prevNode = node;
 7     Node* curNode = node->next;
 8     int count = 1;
 9     while(curNode != NULL)
10     {
11         Node* nextNode = curNode->next;
12         curNode->next = prevNode;
13         prevNode = curNode;
14         curNode = nextNode;
15         count++;
16         if(count == num)
17         {
18             Node* tmp = prev->next;
19             prev->next->next = curNode;
20             prev->next = prevNode;
21             reverseByNum(tmp,curNode,num);
22             return;
23         }
24     }
25     prev->next->next = curNode;
26     prev->next = prevNode;
27 }

 


 

  以上是我的對鏈表的一些認識及理解,如有錯誤歡迎各位指出。

相關文章
相關標籤/搜索