單鏈表的知識點
看完劍指offer和編程之美,將單鏈表的知識點總結如下:
單鏈表所能遇到的知識:
- 單鏈表節點的插入和刪除
- 判斷單鏈表是否有環
- 判斷兩個鏈表是否相交
- 如何快速找到鏈表的中間節點或1/n節點
所能組成的問題:
說明:鏈表的結構體為:
1 typedef struct ListNode_{ 2 int val; 3 struct ListNode_ *next; 4 }ListNode;ListNode
第一種:從無頭單鏈表中刪除節點
假設有一個沒有頭指針的單鏈表。一個指針指向此單鏈表中間的一個節點(不是第一個,也不是最後一個節點),請將該節點從單鏈表中刪除。
例如有如下鏈表:
。。。。。-> A -> B -> C -> D -> E -> 。。。。。。
需要刪除節點C。
解析:若直接刪除節點C,由於不知道B節點的地址,所以不能將節點B和節點D連接起來。故只能“偷梁換柱”,將節點C的值改成節點D的值,然後刪除節點D。
代碼如下:
1 void deleteRandomNode(ListNode *pNode) 2 { 3 assert(pNode != NULL); 4 ListNode *pNext = pNode->next; 5 if(pNext != NULL)deleteRandomNode6 { 7 pNode->val = pNext->val; 8 pNode->next = pNext->next; 9 free(pNext); 10 } 11 }
擴展問題:
編寫一個函數,給定一個鏈表的頭指針,要求只遍歷一遍,將單鏈表中的元素順序反轉過來。
解析:最容易想到的是將所有的值讀取出來,然後進行逆序賦值來達到目的。由於題目要求只遍歷一次,故我們只需要改變表的結構,使其頭結點變成尾節點,尾節點變成頭結點,然後改變指向頭結點的地址即可。
代碼如下:
1 void listReverse(ListNode **pHead) 2 { 3 assert(pHead != NULL); 4 5 ListNode *newpHead = *pHead; 6 ListNode *pNext = newpHead->next; 7 newpHead->next = NULL; 8 while(pNext != NULL) 9 { 10 ListNode *tempNode = pNext; 11 pNext = pNext->next; 12 tempNode->next = newpHead; 13 newpHead = tempNode; 14 } 15 *pHead = newpHead; 16 }listReverse
第二種:判斷單鏈表是否有環
如題,給定一個單鏈表,判斷該鏈表是否有環。
針對單鏈表是否有環這個問題,在C專家編程一書中,P274頁,“怎樣才能檢測到鏈表中存在循環”,說出了四種解法,而且逐層遞進增加難度。這裏只講最後一種解法,使用快慢指針,在每次循環中,快指針每次走兩步,慢指針每次走一步,若有環,則兩者總會相遇,否則,快指針會到結尾。(這裏需要說明的是針對指針,每次使用,都要註意該指針是否為NULL)
代碼如下:
1 /**< 有環返回環內的一個節點,無環返回NULL */ 2 ListNode* hasCirclr(ListNode *pHead) 3 { 4 assert(pHead != NULL); 5 6 if(pHead->next == pHead) 7 return pHead; 8 9 if(pHead->next == NULL) 10 return NULL; 11 12 ListNode *quickNode = pHead->next->next; 13 ListNode *lowerNode = pHead->next; 14 15 while(quickNode != NULL && quickNode != lowerNode) 16 { 17 if(quickNode->next != NULL) 18 { 19 quickNode = quickNode->next->next; 20 lowerNode = lowerNode->next; 21 }else 22 { 23 return NULL; 24 } 25 } 26 27 if(quickNode == lowerNode) 28 { 29 return lowerNode; 30 }else 31 { 32 return NULL; 33 } 34 }hasCirclr
擴展問題:
如果鏈表有環,如何求該鏈表的環的入口地址?
解析,想要確定環的入口地址,就必須讓快慢指針在鏈表的入口地址進行相遇。則,快指針要先走環的長度步,這樣,他們才能在鏈表的入口地址進行相遇。
代碼如下:
1 /**< 有環返回環的入口節點,無環返回NULL */ 2 ListNode* entryCircleNode(ListNode *pHead) 3 { 4 assert(pHead != NULL); 5 6 ListNode *pNode = NULL; 7 if((pNode = hasCirclr(pHead)) == NULL) 8 { 9 return NULL; 10 } 11 12 ListNode *anotherNode = pNode; 13 int cnt = 0;//用於計數環內的節點數量 14 15 do{ 16 anotherNode = anotherNode->next; 17 cnt++; 18 }while(anotherNode != pNode); 19 20 /**< 將anotherNode當成快指針,pNode當成慢指針 */ 21 pNode = anotherNode = pHead; 22 for(int i = 0; i < cnt; i++) 23 { 24 anotherNode = anotherNode->next; 25 } 26 27 while(anotherNode != pNode) 28 { 29 anotherNode = anotherNode->next; 30 pNode = pNode->next; 31 } 32 33 return pNode; 34 }entryCircleNode
第三種:判斷兩個鏈表是否相交
簡化版:給出兩個單項鏈表的頭指針,比如h1,h2,判斷這兩個鏈表是否相交。這裏為了簡化,我們假設兩個鏈表都不帶環。
解析:如果兩個沒有環的鏈表相交於某一節點,那麽在這個節點之後的所有節點都是兩個鏈表所共有的。從而可以知道,如果他們相交,那麽最後一個節點一定是共有的。而我們很容易得到鏈表的最後一個節點。
代碼如下:
1 /**< 兩鏈表相交返回1,否則返回0 */ 2 int isMeet(ListNode *pHead1, ListNode *pHead2) 3 { 4 assert(pHead1 != NULL && pHead2 != NULL); 5 6 while(pHead1->next != NULL) 7 {//找到鏈表1的最後一個節點 8 pHead1 = pHead1->next; 9 } 10 11 while(pHead2->next != NULL) 12 {//找到鏈表2的最後一個節點 13 pHead2 = pHead2->next; 14 } 15 16 if(pHead1 == pHead2) 17 { 18 return 1; 19 }else 20 { 21 return 0; 22 } 23 }isMeet
擴展問題:
1、如果鏈表可能有環呢?
解析:如果兩個鏈表都有環,則在兩鏈表相交的時候,這個環一定是公用的;如果都沒有環,則退化成初始問題;一個有,一個沒有,則肯定不相交。
1 /**< 若是不確定是否有環 */ 2 int isMeetInCircle(ListNode *pHead1, ListNode *pHead2) 3 { 4 assert(pHead1 != NULL && pHead2 != NULL); 5 6 ListNode *circlrNode1 = hasCirclr(pHead1); 7 ListNode *circlrNode2 = hasCirclr(pHead2); 8 if(circlrNode1 == NULL && circlrNode2 == NULL) 9 {/**< 都沒有環 */ 10 return isMeet(pHead1, pHead2); 11 } 12 13 if((circlrNode1 == NULL && circlrNode2 != NULL) || 14 (circlrNode1 != NULL && circlrNode2 == NULL)) 15 {/**< 一個有環,一個沒有環,則肯定不相交 */ 16 return 0; 17 } 18 19 /**< 如果都有環 */ 20 ListNode *pNode = circlrNode1; 21 do{ 22 if(pNode == circlrNode2) 23 return 1; 24 pNode = pNode->next; 25 }while(pNode != circlrNode1); 26 27 return 0; 28 }isMeetInCircle
2、如果我們需要求出兩個鏈表相交的第一個節點呢?
1 /**< 兩鏈表相交返回第一個相交的節點,否則返回NULL */ 2 ListNode* FindFirstMeetNode( ListNode* pHead1, ListNode* pHead2) 3 { 4 assert(pHead1 != NULL && pHead2 != NULL); 5 6 int len1 = 0;//記錄鏈表1的長度 7 int len2 = 0;//記錄鏈表2的長度 8 9 ListNode *pNode; 10 11 for(pNode = pHead1; pNode != NULL; pNode = pNode->next, len1++); 12 for(pNode = pHead2; pNode != NULL; pNode = pNode->next, len2++); 13 14 if(len1 > len2) 15 { 16 int diff = len1 - len2; 17 for(int i = 0; i < diff; i++) 18 { 19 pHead1 = pHead1->next; 20 } 21 }else 22 { 23 int diff = len2 - len1; 24 for(int i = 0; i < diff; i++) 25 { 26 pHead2 = pHead2->next; 27 } 28 } 29 30 31 while(pHead1 != pHead2) 32 { 33 pHead1 = pHead1->next; 34 pHead2 = pHead2->next; 35 } 36 37 return pHead1; 38 }FindFirstMeetNode
第四種:如何快速找到鏈表的中間節點或1/n節點
解析:這個是用快慢指針直接解決。
例如快速找到鏈表的中間節,在每次循環體中,快指針走兩步,慢指針走一步,當快指針走到終點時,慢指針就到鏈表的中間節點了。
測試代碼,僅供參考,有點亂。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <assert.h> 4 5 typedef struct ListNode_{ 6 int val; 7 struct ListNode_ *next; 8 }ListNode; 9 10 void listPrint(ListNode *pHead); 11 void deleteRandomNode(ListNode *pNode); 12 void listReverse(ListNode **pHead); 13 ListNode* hasCirclr(ListNode *pHead); 14 ListNode* entryCircleNode(ListNode *pHead); 15 int isMeet(ListNode *pHead1, ListNode *pHead2); 16 ListNode* FindFirstMeetNode( ListNode* pHead1, ListNode* pHead2); 17 int isMeetInCircle(ListNode *pHead1, ListNode *pHead2); 18 19 int main() 20 { 21 ListNode *pHead = NULL; 22 ListNode *pTail = NULL; 23 ListNode *circleNode = NULL; 24 for(int i = 0; i < 17; i++) 25 { 26 ListNode *newNode = (ListNode*)malloc(sizeof(ListNode)); 27 newNode->val = i + 1; 28 newNode->next = NULL; 29 if(newNode == NULL) 30 exit(1); 31 if(i == 0) 32 { 33 pHead = pTail = newNode; 34 }else 35 { 36 pTail->next = newNode; 37 pTail = newNode; 38 } 39 40 if(i == 8) 41 { 42 circleNode = newNode; 43 } 44 } 45 46 listPrint(pHead); 47 48 // deleteRandomNode(pHead->next->next); 49 // 50 // listPrint(pHead); 51 52 // listReverse(&pHead); 53 // 54 // listPrint(pHead); 55 56 pTail->next = circleNode; 57 58 59 //listPrint(pHead); 60 61 // ListNode *Node = hasCirclr(pHead); 62 63 // ListNode *Node = entryCircleNode(pHead); 64 // 65 // if(Node != NULL) 66 // printf("%d\n", Node->val); 67 // else 68 // printf("No circlr!\n"); 69 70 ListNode *pHead2 = NULL; 71 ListNode *pTail2 = NULL; 72 ListNode *circleNode2 = NULL; 73 for(int i = 0; i < 15; i++) 74 { 75 ListNode *newNode = (ListNode*)malloc(sizeof(ListNode)); 76 newNode->val = i + 30; 77 newNode->next = NULL; 78 if(newNode == NULL) 79 exit(1); 80 if(i == 0) 81 { 82 pHead2 = pTail2 = newNode; 83 }else 84 { 85 pTail2->next = newNode; 86 pTail2 = newNode; 87 } 88 89 if(i == 8) 90 { 91 circleNode2 = newNode; 92 } 93 } 94 listPrint(pHead2); 95 //pTail2->next = circleNode; 96 // listPrint(pHead2); 97 // printf("%d\n", isMeet(pHead, pHead2)); 98 99 // ListNode *Node = FindFirstMeetNode(pHead, pHead2); 100 // printf("%d\n", Node->val); 101 102 pTail2->next = pTail; 103 printf("%d\n", isMeetInCircle(pHead, pHead2)); 104 105 106 107 return 0; 108 } 109 110 void listPrint(ListNode *pHead) 111 { 112 assert(pHead != NULL); 113 while(pHead) 114 { 115 printf("%d->", pHead->val); 116 pHead = pHead->next; 117 } 118 printf("\b\b \n"); 119 }mian_test
單鏈表的知識點