[C語言]單向鏈表的構建以及翻轉算法
node
單向鏈表的連接方向是單向的,其中每一個結點都有指針成員變量指向鏈表中的下一個結點,訪問鏈表時要從頭節點(帶頭節點的鏈表)或存儲首個數據的節點(不帶頭節點的鏈表)開始順序查詢。本文將以帶頭結點的非循環單向鏈表爲例,其鏈表模型以下:算法
其中head爲頭結點(不存儲數據)、data節點存儲數據、pNext存儲下一節點的地址。
ide
當單項鍊表不包含頭結點時,鏈表首個節點即是所存儲的第一個數據的節點;當單項鍊表是循環鏈表時,鏈表中存儲最後一個數據的節點中pNext指向的不是NULL而是鏈表首部。
spa
一、單項鍊表節點結構體的定義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;
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 }
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 }
三、 單項鍊表的斷定
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 }
四、單向鏈表的查詢
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 }
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 }
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 }
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 }
五、 單項鍊表的節點插入
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 }
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 }
六、單項鍊表的數據刪除
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 }
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 }
一、單項鍊表的總體翻轉
單向鏈表的總體翻轉具體步驟見「代碼註釋」,其演示模型以下(建議先了解代碼):
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 }
以上是我的對鏈表的一些認識及理解,如有錯誤歡迎各位指出。