1. 程式人生 > >單鏈表的知識點

單鏈表的知識點

and 知識 -a clr stdio.h 給定 lse 四種 解決

看完劍指offer和編程之美,將單鏈表的知識點總結如下:

單鏈表所能遇到的知識:

  1. 單鏈表節點的插入和刪除
  2. 判斷單鏈表是否有環
  3. 判斷兩個鏈表是否相交
  4. 如何快速找到鏈表的中間節點或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)
6 { 7 pNode->val = pNext->val; 8 pNode->next = pNext->next; 9 free(pNext); 10 } 11 }
deleteRandomNode

  擴展問題:

    編寫一個函數,給定一個鏈表的頭指針,要求只遍歷一遍,將單鏈表中的元素順序反轉過來。

  解析:最容易想到的是將所有的值讀取出來,然後進行逆序賦值來達到目的。由於題目要求只遍歷一次,故我們只需要改變表的結構,使其頭結點變成尾節點,尾節點變成頭結點,然後改變指向頭結點的地址即可。

  代碼如下:

技術分享圖片
 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

  

單鏈表的知識點