单链表的知识点

Posted fenghualong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单链表的知识点相关的知识,希望对你有一定的参考价值。

看完剑指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
", Node->val);
 67 //    else
 68 //        printf("No circlr!
");
 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
", isMeet(pHead, pHead2));
 98 
 99 //    ListNode *Node = FindFirstMeetNode(pHead, pHead2);
100 //    printf("%d
", Node->val);
101 
102     pTail2->next = pTail;
103     printf("%d
", 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("  
");
119 }
mian_test

 

 

  

以上是关于单链表的知识点的主要内容,如果未能解决你的问题,请参考以下文章

单链表知识点汇总

数据结构代码(用C语言) 单链表的插入和删除

数据结构单链表的增删查改,附代码+笔记gitee自取

单链表及单链表的三个初级算法思想

单链表及单链表的三个初级算法思想

单链表~增删查改(附代码)~简单实现