单链表的知识点
Posted fenghualong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单链表的知识点相关的知识,希望对你有一定的参考价值。
看完剑指offer和编程之美,将单链表的知识点总结如下:
单链表所能遇到的知识:
- 单链表节点的插入和删除
- 判断单链表是否有环
- 判断两个链表是否相交
- 如何快速找到链表的中间节点或1/n节点
所能组成的问题:
说明:链表的结构体为:
1 typedef struct ListNode_{ 2 int val; 3 struct ListNode_ *next; 4 }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 }
扩展问题:
编写一个函数,给定一个链表的头指针,要求只遍历一遍,将单链表中的元素顺序反转过来。
解析:最容易想到的是将所有的值读取出来,然后进行逆序赋值来达到目的。由于题目要求只遍历一次,故我们只需要改变表的结构,使其头结点变成尾节点,尾节点变成头结点,然后改变指向头结点的地址即可。
代码如下:
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 }
第二种:判断单链表是否有环
如题,给定一个单链表,判断该链表是否有环。
针对单链表是否有环这个问题,在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 }
扩展问题:
如果链表有环,如何求该链表的环的入口地址?
解析,想要确定环的入口地址,就必须让快慢指针在链表的入口地址进行相遇。则,快指针要先走环的长度步,这样,他们才能在链表的入口地址进行相遇。
代码如下:
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 }
第三种:判断两个链表是否相交
简化版:给出两个单项链表的头指针,比如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 }
扩展问题:
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 }
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 }
第四种:如何快速找到链表的中间节点或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 }
以上是关于单链表的知识点的主要内容,如果未能解决你的问题,请参考以下文章