当前位置:   article > 正文

【数据结构】链表-C语言版_顺序表链表的应用错误及原因

顺序表链表的应用错误及原因

目录

顺序表缺点

链表的概念和结构

链表的分类

链表的实现

链表应用OJ题

顺序表和链表优缺点对比


顺序表缺点

顺序表随机访问很方便,但是也会有不足啊:

(1)挪动数据时间开销较大:头部/中间的插入删除,需要挪动后面的所有数据,时间复杂度为O(N)

(2)增容有代价:增容需要重新申请空间,拷贝数据,释放旧空间,系统消耗不小

(3)空间浪费:增容一般增至原来的2倍大空间,会有空间浪费,假如当前容量为100,满了以后增容到200,再继续插入了5个数据,后面没有插入数据了,这就浪费了95个空间。

不存在扩容代价、不存在空间浪费、按需申请空间、头部或者中间插入数据而不需要挪动数据的链表可以解决以上问题。

链表的概念和结构

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。

 注意:

(1)链式结构在逻辑上连续,逻辑上不一定连续。(逻辑结构是想象的,物理结构是内存中实际存储的)。

(2)结点一般从堆上申请。

(3)堆上申请的空间时按照一定策略分配的,两次申请的空间可能连续也可能不连续。

链表的分类

有8种链表结构 :

(1)单向和双向

 (2)带头单链表和不带头单链表

 

 (3)非循环链表和循环链表

 以上3种分类的链表一共有2*2*2=8种组合,最实用的有两种:

(1)无头单向非循环链表:结构简单,一般不会用来单独存储数据。十几种更多是作为其他数据结构的子结构,如哈希桶,图的邻接表等。

(2)带头双向循环链表:结构最复杂,一般用来单独存储数据。实际中使用的链表数据结构都是带头双向循环链表。虽然结构复杂,但是使用代码实现以后会发现该结构会带来很多优势,实现反而简单。

链表的实现

如果需要交换两个int的值,那么swap函数的实参是两个int的地址,形参是两个int型指针。

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include<stdio.h>
  3. void swap(int* pa, int* pb)//用两个指针作为形参接收两个地址
  4. {
  5. int temp = *pa;
  6. *pa = *pb;
  7. *pb = temp;
  8. }
  9. int main()
  10. {
  11. int a = 3;
  12. int b = 5;
  13. swap(&a, &b);//实参是两个int的地址
  14. printf("a = %d\n", a);
  15. printf("a = %d\n", b);
  16. return 0;
  17. }

同理,对于链表的增删,如果需要改变指针指向,就要传指针的地址,即二级指针。

链表实现:

025-SList.h 链表函数定义

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #pragma once
  3. typedef int SLTDataType;
  4. typedef struct SListNode
  5. {
  6. SLTDataType data;
  7. struct SListNode* next;
  8. }SLTNode;
  9. //单向+不带头+非循环
  10. //打印
  11. void SListPrint(SLTNode* plist);
  12. //尾插(需要改变指针指向,就要传指针的地址,即二级指针,如需要交换两个int的值,那么swap函数的实参是两个int的地址,形参是两个int型指针)
  13. void SListPushBack(SLTNode** plist,SLTDataType x);
  14. //头插
  15. void SListPushFront(SLTNode** plist, SLTDataType x);
  16. //尾删
  17. void SListPopBack(SLTNode** plist);
  18. //头删
  19. void SListPopFront(SLTNode** plist);
  20. //单链表查找
  21. SLTNode* SListFind(SLTNode* plist, SLTDataType x);
  22. //在pos之后插入x
  23. void SListInsertAfter(SLTNode* pos, SLTDataType x);
  24. //在pos之前插入x
  25. void SListInsertBefore(SLTNode** plist,SLTNode* pos, SLTDataType x);
  26. //在pos之前插入x,没有头的情况:后插一个值为x的节点,再跟前面的结点值交换。
  27. void SListInsertBeforeNoHead(SLTNode* pos, SLTDataType x);
  28. //删除后一个节点
  29. void SListEraseAfter(SLTNode* pos);
  30. //删除当前位置
  31. void SListEraseCur(SLTNode** plist, SLTNode* pos);

 025-SList.c 链表函数实现

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #pragma once
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<assert.h>
  6. #include "025-SList.h"
  7. //打印
  8. void SListPrint(SLTNode* plist)
  9. {
  10. SLTNode* cur = plist;
  11. //找空节点,找到后不再打印
  12. while (cur != NULL)
  13. {
  14. printf("%d->", cur->data);
  15. //让cur指向下一个节点
  16. cur = cur->next;
  17. }
  18. printf("NULL\n");
  19. }
  20. //创建新节点
  21. SLTNode* BuySLTNode(SLTDataType x)
  22. {
  23. //申请空间
  24. SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
  25. if (node == NULL)
  26. {
  27. return NULL;
  28. }
  29. //数据赋值
  30. node->data = x;
  31. //指针赋值
  32. node->next = NULL;
  33. return node;
  34. }

(1)尾插:

  1. //尾插
  2. void SListPushBack(SLTNode** pplist, SLTDataType x)
  3. {
  4. SLTNode* newNode = BuySLTNode(x);
  5. //不能使用tail->next != NULL直接作为判断条件,因为有可能是空链表,对空指针解引用会造成非法访问,因此要分两种情况
  6. if (*pplist == NULL)//链表为空
  7. {
  8. *pplist = newNode;
  9. }
  10. else //链表不为空
  11. {
  12. SLTNode* tail = *pplist;
  13. //找尾节点
  14. while (tail->next != NULL)
  15. {
  16. tail = tail->next;
  17. }
  18. tail->next = newNode;
  19. }
  20. }

(2)头插,不需要区分空链表和非空链表

  1. //头插
  2. void SListPushFront(SLTNode** pplist, SLTDataType x)
  3. {
  4. SLTNode* newNode = BuySLTNode(x);
  5. newNode->next = *pplist;//新节点的下一个指向第一个节点
  6. *pplist = newNode;//链表指向新节点
  7. }

(3)尾删

  1. //尾删
  2. void SListPopBack(SLTNode** pplist)
  3. {
  4. //链表为空
  5. if (*pplist == NULL)
  6. {
  7. return;
  8. }
  9. //链表只有一个结点
  10. else if ((*pplist)->next == NULL)
  11. {
  12. free(*pplist);
  13. *pplist = NULL;
  14. }
  15. //链表有多个结点
  16. else
  17. {
  18. SLTNode* pre = NULL;//后面用该结点指向尾节点的前驱结点,因为删除为节点后,尾节点的前驱结点的后继结点要赋为空指针
  19. SLTNode* tail = *pplist;
  20. //寻找最后一个结点,循环结束后,tail为尾结点,pre为尾结点的前驱结点
  21. while (tail->next != NULL)
  22. {
  23. pre = tail;
  24. tail = tail->next;
  25. }
  26. free(tail);
  27. tail = NULL;
  28. pre->next = NULL;
  29. }
  30. }

(4)头删

  1. //头删
  2. void SListPopFront(SLTNode** pplist)
  3. {
  4. if (*pplist == NULL)
  5. {
  6. return;
  7. }
  8. else
  9. {
  10. SLTNode* next = (*pplist)->next;//保存后一个节点
  11. free(*pplist);
  12. *pplist = next;//链表指向下一个节点
  13. }
  14. }

(5)单链表查找

  1. //单链表查找
  2. SLTNode* SListFind(SLTNode* plist, SLTDataType x)
  3. {
  4. SLTNode* cur = plist;
  5. while (cur)//没找到就往后继续走
  6. {
  7. if (cur->data == x)
  8. {
  9. return cur;//找到了就返回
  10. }
  11. cur = cur->next;
  12. }
  13. return NULL;
  14. }

(6) 在pos之后插入

  1. //在pos之后插入
  2. void SListInsertAfter(SLTNode* pos, SLTDataType x)
  3. {
  4. assert(pos);
  5. SLTNode* newNode = BuySLTNode(x);//插入需要先创建结点
  6. newNode->next = pos->next;//新节点的下一个指向pos的下一个
  7. pos->next = newNode;//pos的下一个指向新节点
  8. }

(7)在pos之前插入x

  1. //在pos之前插入x
  2. void SListInsertBefore(SLTNode** pplist, SLTNode* pos, SLTDataType x)
  3. {
  4. assert(pos);
  5. SLTNode* newNode = BuySLTNode(x);
  6. //第一个节点就是pos,即头插
  7. if (pos == *pplist)
  8. {
  9. newNode->next = pos;
  10. *pplist = newNode;
  11. }
  12. else
  13. {
  14. SLTNode* prev = NULL;//用来记录pos的前一个结点
  15. SLTNode* cur = *pplist;
  16. while (cur != pos)//找pos
  17. {
  18. prev = cur;
  19. cur = cur->next;
  20. }
  21. newNode->next = cur;//新节点的下一个指向pos,此时跳出while循环的cur=pos
  22. prev->next = newNode;//前一个结点的下一个指向新节点
  23. }
  24. }

(8)在pos之前插入x,没有头(plist):后插一个值为x的节点,再跟前面的结点值交换

  1. //在pos之前插入x,没有头(plist)的情况:后插一个值为x的节点,再跟前面的结点值交换。
  2. void SListInsertBeforeNoHead(SLTNode* pos, SLTDataType x)
  3. {
  4. assert(pos);
  5. SLTNode* newNode = BuySLTNode(x);
  6. //后插新节点
  7. newNode->next = pos->next;
  8. pos->next = newNode;
  9. //将新结点的值和pos的值进行交换
  10. SLTDataType temp = pos->data;
  11. newNode->data = temp;
  12. pos->data = x;
  13. }

 (9)删除后一个结点

  1. //删除后一个节点
  2. void SListEraseAfter(SLTNode* pos)
  3. {
  4. assert(pos);
  5. if (pos->next == NULL)
  6. {
  7. return;
  8. }
  9. SLTNode* next = pos->next;
  10. pos->next = next->next;//pos的下一个指向pos的下一个结点的下一个结点
  11. free(next);
  12. next = NULL;
  13. }

(10) 删除当前位置

  1. //删除当前位置
  2. void SListEraseCur(SLTNode** pplist, SLTNode* pos)
  3. {
  4. assert(pos);
  5. SLTNode* prev = NULL;
  6. SLTNode* cur = *pplist;
  7. while (cur != pos)//寻找pos
  8. {
  9. prev = cur;
  10. cur = cur->next;
  11. }
  12. prev->next = cur->next;//前一个结点的下一个指向当前结点即pos的下一个
  13. free(cur);
  14. cur = NULL;
  15. }

 025-test.c 测试代码

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include<stdio.h>
  3. #include<assert.h>
  4. #include "025-SList.h"
  5. void TestSList1()
  6. {
  7. SLTNode* plist = NULL;
  8. SListPushBack(&plist, 1);
  9. SListPushBack(&plist, 2);
  10. SListPushBack(&plist, 3);
  11. SListPushBack(&plist, 4);
  12. SListPrint(plist);
  13. SListPushFront(&plist, 0);
  14. SListPrint(plist);
  15. SListPopBack(&plist);
  16. SListPrint(plist);
  17. SListPopBack(&plist);
  18. SListPrint(plist);
  19. SListPopBack(&plist);
  20. SListPrint(plist);
  21. SListPopBack(&plist);
  22. SListPrint(plist);
  23. }
  24. void TestSList2()
  25. {
  26. SLTNode* plist = NULL;
  27. SListPushBack(&plist, 1);
  28. SListPushBack(&plist, 2);
  29. SListPushBack(&plist, 3);
  30. SListPushBack(&plist, 4);
  31. SListPrint(plist);
  32. SListPopFront(&plist);
  33. SListPopFront(&plist);
  34. SListPopFront(&plist);
  35. SListPopFront(&plist);
  36. SListPopFront(&plist);
  37. SListPrint(plist);
  38. }
  39. void TestSList3()
  40. {
  41. SLTNode* plist = NULL;
  42. SListPushBack(&plist, 1);
  43. SListPushBack(&plist, 2);
  44. SListPushBack(&plist, 3);
  45. SListPushBack(&plist, 4);
  46. SListPrint(plist);
  47. SLTNode* pos = SListFind(plist, 3);
  48. if (pos)
  49. {
  50. //单链表查找兼具修改功能
  51. pos->data = 100;
  52. printf("找到了\n");
  53. }
  54. else
  55. {
  56. printf("没找到\n");
  57. }
  58. SListPrint(plist);
  59. }
  60. void TestSList4()
  61. {
  62. SLTNode* plist = NULL;
  63. SListPushBack(&plist, 1);
  64. SListPushBack(&plist, 2);
  65. SListPushBack(&plist, 3);
  66. SListPushBack(&plist, 4);
  67. SListPrint(plist);
  68. SLTNode* pos = SListFind(plist, 3);
  69. SListInsertAfter(pos, 300);
  70. SListPrint(plist);
  71. SListInsertBefore(&plist, pos,400);
  72. SListPrint(plist);
  73. SListEraseCur(&plist, pos);
  74. SListPrint(plist);
  75. }
  76. int main()
  77. {
  78. TestSList4();
  79. return 0;
  80. }

执行结果:

链表应用OJ题

 1.删除链表中等于给定值val的所有节点,OJ链接

分析:

(1)要删除某一结点,需要保存该结点的前一个结点(删除当前节点后,前一结点应指向当前结点的下一结点),同时需要保存当前结点的下一结点(删除当前结点后,能够继续向后访问该链表)

(2)操作:

        遍历链表,如果当前结点值==val,就保存当前结点的下一个结点,前一结点指向当前结点的下一结点,释放当前结点,当前结点指向下一结点;

        如果当前结点值!=val,前一结点挪到当前结点位置,当前结点挪到下一结点位置。需要考虑特殊情况,第一个结点值 == val时,此时prev=NULL需要特殊处理。

不带哨兵位头结点解法:

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * struct ListNode *next;
  6. * };
  7. */
  8. struct ListNode* removeElements(struct ListNode* head, int val){
  9. struct ListNode* prev = NULL;
  10. struct ListNode* cur = head;
  11. while(cur)
  12. {
  13. if(cur->val == val)//找到了
  14. {
  15. struct ListNode* next = cur->next;//free(cur),就必须要先保存cur的下一个结点,否则无法继续访问下一结点
  16. if(prev == NULL)//cur是头,要删除的结点在第一个位置
  17. {
  18. free(cur);
  19. head = next;
  20. cur = next;
  21. }
  22. else
  23. {
  24. prev->next = next;
  25. free(cur);
  26. cur = next;
  27. }
  28. }
  29. else
  30. {
  31. prev = cur;
  32. cur = cur->next;
  33. }
  34. }
  35. return head;
  36. }

带哨兵位头结点解法:

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * struct ListNode *next;
  6. * };
  7. */
  8. struct ListNode* removeElements(struct ListNode* head, int val)
  9. {
  10. struct ListNode* guardHead = (struct ListNode*)malloc(sizeof(struct ListNode));
  11. guardHead->next = head;
  12. struct ListNode* prev = guardHead;
  13. struct ListNode* cur = head;
  14. while (cur)
  15. {
  16. if (cur->val == val)
  17. {
  18. struct ListNode* next = cur->next;
  19. prev->next = next;
  20. free(cur);
  21. cur = next;
  22. }
  23. else
  24. {
  25. prev = cur;
  26. cur = cur->next;
  27. }
  28. }
  29. head = guardHead->next;
  30. free(guardHead);
  31. guardHead = NULL;
  32. return head;
  33. }

2.反转一个单链表 OJ链接

分析:

解法一:如果只定义两个节点,当n2指向n1时,n2的下一个结点就找不到了。因此要定义3个节点,n1和n2用于翻转,n3用于迭代:n1指向NULL,n2指向n1,n3用于记录n2的下一个结点。

 

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * struct ListNode *next;
  6. * };
  7. */
  8. struct ListNode* reverseList(struct ListNode* head)
  9. {
  10. if(head == NULL || head->next == NULL)
  11. {
  12. return head;
  13. }
  14. struct ListNode* n1 = NULL,*n2 = head,*n3 = head->next;
  15. while(n2)
  16. {
  17. n2->next = n1;
  18. n1 = n2;
  19. n2 = n3;
  20. if(n3)
  21. {
  22. n3 = n3->next;
  23. }
  24. }
  25. return n1;
  26. }

解法二:头插法,依次取原链表中的节点头插到新节点

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * struct ListNode *next;
  6. * };
  7. */
  8. struct ListNode* reverseList(struct ListNode* head)
  9. {
  10. struct ListNode* cur = head;
  11. struct ListNode* newHead = NULL;
  12. while(cur)
  13. {
  14. struct ListNode* next = cur->next;
  15. cur->next = newHead;
  16. newHead = cur;
  17. cur = next;
  18. }
  19. return newHead;
  20. }

3.链表的中间结点   OJ链接

 分析:只能遍历一遍,可以用快慢指针,慢指针每次走一步,快指针每次走两步 

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * struct ListNode *next;
  6. * };
  7. */
  8. struct ListNode* middleNode(struct ListNode* head)
  9. {
  10. struct ListNode* slow = head;
  11. struct ListNode* fast = head;
  12. while(fast && fast->next)
  13. {
  14. slow = slow->next;
  15. fast = fast->next->next;
  16. }
  17. return slow;
  18. }

4.输出链表倒数第k个结点,OJ链接

分析:如何找倒数第k个结点,可以使用快慢指针 ,让快指针先走k步,再让慢指针开始走,此时快慢指针相差k步,然后快慢指针一起向后移动,等快指针移动到链表末尾NULL时,慢指针的位置就是倒数第k个结点位置。

  1. struct ListNode
  2. {
  3. int val;
  4. struct ListNode *next;
  5. };
  6. struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
  7. {
  8. if(pListHead == NULL)
  9. {
  10. return NULL;
  11. }
  12. struct ListNode* slow = pListHead;
  13. struct ListNode* fast = pListHead;
  14. while(k--)
  15. {
  16. if(fast == NULL)
  17. {
  18. return NULL;
  19. }
  20. fast = fast->next;
  21. }
  22. while(fast)
  23. {
  24. slow = slow->next;
  25. fast = fast->next;
  26. }
  27. return slow;
  28. }

5.合并两个有序链表 OJ链接

分析:尾插法,找第一个结点小的链表当作新表头,找尾比较麻烦,需要遍历,因此直接定义一个尾节点tail,每次让tail指向刚插入的节点,当有一个链表走完时就停下,再把没走完的链表全部连接到tail上。

  1. struct ListNode {
  2. int val;
  3. struct ListNode *next;
  4. };
  5. struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
  6. {
  7. struct ListNode* head,*tail = NULL;
  8. if(l1 == NULL)
  9. {
  10. return l2;
  11. }
  12. if(l2 == NULL)
  13. {
  14. return l1;
  15. }
  16. if(l1->val < l2->val)
  17. {
  18. head = tail = l1;
  19. l1 = l1->next;
  20. }
  21. else
  22. {
  23. head = tail = l2;
  24. l2 = l2->next;
  25. }
  26. while(l1 && l2)
  27. {
  28. if(l1->val < l2->val)
  29. {
  30. tail->next = l1;
  31. l1 = l1->next;
  32. tail = tail->next;
  33. }
  34. else
  35. {
  36. tail->next = l2;
  37. l2 = l2->next;
  38. tail = tail->next;
  39. }
  40. }
  41. if(l1 == NULL)
  42. {
  43. tail->next = l2;
  44. }
  45. else
  46. {
  47. tail->next = l1;
  48. }
  49. return head;
  50. }

6.编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 OJ链接

分析:申请两个链表,一个放比x小的节点,一个放比x大的节点,最后将大链表链接在小链表末尾即可。

  1. struct ListNode {
  2. int val;
  3. struct ListNode *next;
  4. ListNode(int x) : val(x), next(NULL) {}
  5. };
  6. class Partition {
  7. public:
  8. ListNode* partition(ListNode* pHead, int x) {
  9. // 有头指针
  10. struct ListNode* lessHead,* lessTail,*greaterHead,*greaterTail;
  11. lessHead = lessTail = (struct ListNode*)malloc(sizeof(struct ListNode));
  12. greaterHead = greaterTail = (struct ListNode*)malloc(sizeof(struct ListNode));
  13. lessTail->next = NULL;
  14. greaterTail->next = NULL;
  15. struct ListNode *cur = pHead;
  16. while(cur)
  17. {
  18. if(cur->val<x)
  19. {
  20. lessTail->next = cur;
  21. lessTail = lessTail->next;
  22. }
  23. else
  24. {
  25. greaterTail->next = cur;
  26. greaterTail = greaterTail->next;
  27. }
  28. cur = cur->next;
  29. }
  30. lessTail->next = greaterHead->next;
  31. greaterTail->next = NULL;
  32. pHead = lessHead->next;
  33. free(lessHead);
  34. free(greaterHead);
  35. return pHead;
  36. }
  37. };

7.链表的回文结构 OJ链接

分析:判断链表是否是回文结构,可以结合前面的题:

(1)利用快慢指针找到链表中间结点

(2)将后半部分逆置

(3)将(2)中的链表从第一个节点开始和中间结点开始同时进行访问,如果所有val相等,则链表为回文结构。

  1. struct ListNode {
  2. int val;
  3. struct ListNode *next;
  4. ListNode(int x) : val(x), next(NULL) {}
  5. };
  6. struct ListNode* middleNode(struct ListNode* head)
  7. {
  8. // write code here
  9. struct ListNode* slow = head;
  10. struct ListNode* fast = head;
  11. while(fast && fast->next)
  12. {
  13. slow = slow->next;
  14. fast = fast->next->next;
  15. }
  16. return slow;
  17. }
  18. struct ListNode* reverseList(struct ListNode* head)
  19. {
  20. struct ListNode* cur = head;
  21. struct ListNode* newHead = NULL;
  22. while(cur)
  23. {
  24. struct ListNode* next = cur->next;
  25. cur->next = newHead;
  26. newHead = cur;
  27. cur = next;
  28. }
  29. return newHead;
  30. }
  31. bool chkPalindrome(ListNode* A)
  32. {
  33. struct ListNode* mid = middleNode(A);
  34. struct ListNode* rHead = reverseList(mid);
  35. while(A && rHead)
  36. {
  37. if(A->val != rHead->val)
  38. {
  39. return false;
  40. }
  41. else
  42. {
  43. A = A->next;
  44. rHead = rHead -> next;
  45. }
  46. }
  47. return true;
  48. }

8.输入两个链表,找出它们的第一个公共结点  OJ链表

分析:判断链表是否相交,是判断是否有两个节点地址相同,而不是判断是否有两个结点值相同。

(1)若只是判断两个链表是否相交,直接判断最后一个链表是否相等即可

(2)要找出相交的结点:

        a.计算两个链表的长度,计算两个链表的长度差距

        b.让长链表先走差距步,再让两个链表同时向前走,若有相同的结点即相交。

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * struct ListNode *next;
  6. * };
  7. */
  8. struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
  9. {
  10. if(headA == NULL || headB == NULL)
  11. {
  12. return NULL;
  13. }
  14. struct ListNode *curA = headA, *curB = headB;
  15. int lenA = 0,lenB = 0;
  16. while(curA->next)
  17. {
  18. curA = curA->next;
  19. lenA++;
  20. }
  21. while(curB->next)
  22. {
  23. curB = curB->next;
  24. lenB++;
  25. }
  26. if(curA != curB)
  27. {
  28. return NULL;
  29. }
  30. struct ListNode *longList = headA,*shortList = headB;
  31. if(lenA < lenB)
  32. {
  33. longList = headB;
  34. shortList = headA;
  35. }
  36. int gap = abs(lenA - lenB);
  37. while(gap--)
  38. {
  39. longList = longList->next;
  40. }
  41. while(longList != shortList)
  42. {
  43. longList = longList->next;
  44. shortList = shortList->next;
  45. }
  46. return longList;
  47. }

9.给定一个链表,判断链表中是否有环 OJ链接

分析:

如何判断链表是否有环:使用快慢指针,慢指针一次走1步,快指针一次走2步,如果链表带环,那么快慢同时从链表起始位置开始向后走,一定会在环内相遇,此时快慢指针都有可能在环内打圈,直到相遇;否则,如果链表不带环,那么快指针会先走到链表末尾,慢指针只能在链表末尾追上快指针。

  1. struct ListNode {
  2. int val;
  3. struct ListNode *next;
  4. };
  5. bool hasCycle(struct ListNode *head)
  6. {
  7. struct ListNode *slow = head;
  8. struct ListNode *fast = head;
  9. while(fast && fast->next)
  10. {
  11. slow = slow->next;
  12. fast = fast->next->next;
  13. if(slow == fast)
  14. {
  15. return true;
  16. }
  17. }
  18. return false;
  19. }

如果快指针不是一次走2步,而是一次走3步,一次走4步一次走x步呢?能不能判断出链表是否带环呢?

        如果快指针一次走两步,当slow从直线中间移动到直线末尾时,fast又走了slow的2倍,因此当slow进环时,fast可能在环的任意位置,具体要看直线有多长,环有多大。在环内,一定是fast追slow,因为fast比slow移动的快。

         fast一次走3步:假设slow进环的时候,fast跟slow相差N步,环的长度为C,追击时,slow走1步,fast走3步,每走1次,差距就缩小2,如下图所示:

 因此对于N,如果N为奇数,一定不会相遇  ,差距为:N,N-2,N-4,···,1,-1(-1即C-1,一直是fast在追slow)

 如果N为奇数,一定会相遇 ,差距为:N,N-2,N-4,···,2,0

 那么根据以上推论,如果fast一次走4步, 如果N为奇数,差距为:N,N-3,N-6,···,4,1,-2, 如果N为奇数,差距为:N,N-3,N-6,···,5,2,-1

总结:如果slow进环时,slow和fast的差距N是奇数,且环的长度C为偶数(则C-1为奇数,上面举例可以看出差距最小为1或-1),那么就永远追不上了。

10.给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL   OJ链接

如何求环的入口点?假设起点到入口点的距离是L,假设相遇点到入口点的距离是X,假设环的大小是C,相遇时,快指针已经在环内打转了N圈,那么慢指针走的路程为L+X,快指针走的路程为L+N*C+X,则有以下公式恒成立:

根据以上公式,则有L=N*C-X。因此,如果让快指针从相遇点出发,慢指针从起点出发,它们会在入口点相遇,此时快指针已经在圈内打转了N圈,快指针走的路程为N*C-X,慢指针走的路程为L。

  1. struct ListNode {
  2. int val;
  3. struct ListNode *next;
  4. };
  5. struct ListNode* detectCycle(struct ListNode* head)
  6. {
  7. struct ListNode* slow = head;
  8. struct ListNode* fast = head;
  9. while (fast && fast->next)
  10. {
  11. slow = slow->next;
  12. fast = fast->next->next;
  13. if (slow == fast)
  14. {
  15. struct ListNode* meet = fast;
  16. while (meet != head)
  17. {
  18. meet = meet->next;
  19. head = head->next;
  20. }
  21. return meet;
  22. }
  23. }
  24. return NULL;
  25. }

11.给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。要求返回这个链表的深度拷贝。OJ链接

(1)next如何处理?直接将结点拷贝下来是有问题的,把7拷贝下来指向的还是原来的13,而不是新创建的结点13,虽然值都是13,但是地址却不同。

所以要malloc之后,把值拷贝出来,然后把拷贝结点7的next指向原结点13。

(2)拷贝节点的random如何处理?如果直接处理,效率太低

        a.拷贝以后,新链表需要确定每个random,需要去链表中查找,时间复杂度是O(N),每个random都要处理就是O(N*N),效率低

        b.如果链表中存在节点的值相同,那么就更复杂了,比如有多个节点的值是7,那么原链表random有几个指向7的节点呢?新链表的random分别对应哪个7呢?毕竟各个7的地址不同。

        把拷贝结点链接在原结点的后面,当找到原结点13的random结点7就能找到原结点7的拷贝结点,因为拷贝结点就是链接在原结点的后面,原结点13的random指向原结点7,拷贝结点13的random指向拷贝结点7。拷贝结点的random等于原结点random的next,即找到原结点就找到拷贝结点了。

(3)不能先让原结点7指向拷贝7,否则原结点7找不到原结点13了,应该让原结点7的copy结点7指向原结点7的next结点13。

  1. /**
  2. * Definition for a Node.
  3. * struct Node {
  4. * int val;
  5. * struct Node *next;
  6. * struct Node *random;
  7. * };
  8. */
  9. struct Node* copyRandomList(struct Node* head)
  10. {
  11. if (head == NULL)
  12. {
  13. return NULL;
  14. }
  15. //1.将拷贝结点挂在原结点后面,建立对应关系
  16. struct Node* cur = head;
  17. while (cur)
  18. {
  19. struct Node* next = cur->next;
  20. struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
  21. copy->val = cur->val;
  22. cur->next = copy;
  23. copy->next = next;
  24. cur = next;
  25. }
  26. //2.处理copy结点的random
  27. cur = head;
  28. while (cur)
  29. {
  30. struct Node* copy = cur->next;
  31. if (cur->random == NULL)
  32. {
  33. copy->random = NULL;
  34. }
  35. else
  36. {
  37. copy->random = cur->random->next;
  38. }
  39. cur = copy->next;
  40. }
  41. //3.将拷贝结点取下来链接到一起,恢复原链表
  42. cur = head;
  43. struct Node* copyHead, * copyTail;
  44. copyHead = copyTail = (struct Node*)malloc(sizeof(struct Node));
  45. while (cur)
  46. {
  47. struct Node* copy = cur->next;
  48. struct Node* next = copy->next;
  49. //尾插
  50. copyTail->next = copy;
  51. copyTail = copyTail->next;
  52. cur = next;
  53. }
  54. struct Node* guard = copyHead;
  55. copyHead = copyHead->next;
  56. free(guard);
  57. return copyHead;
  58. }

 12.链表的插入排序  OJ链接

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * struct ListNode *next;
  6. * };
  7. */
  8. struct ListNode* insertionSortList(struct ListNode* head)
  9. {
  10. //1.初始条件
  11. struct ListNode* sortHead = head;
  12. struct ListNode* cur = head->next;
  13. sortHead->next = NULL;
  14. while(cur)//2.终止条件
  15. {
  16. //3.迭代条件
  17. struct ListNode* next = cur->next;
  18. //将cur结点插入到前面有序区间
  19. struct ListNode* p = NULL, * c = sortHead;
  20. while(c)
  21. {
  22. if(cur->val < c->val)
  23. {
  24. break;
  25. }
  26. else
  27. {
  28. p = c;
  29. c = c->next;
  30. }
  31. }
  32. if(p == NULL)
  33. {
  34. cur->next = c;
  35. sortHead = cur;
  36. }
  37. else
  38. {
  39. p->next = cur;
  40. cur->next = c;
  41. }
  42. cur = next;
  43. }
  44. return sortHead;
  45. }

顺序表和链表优缺点对比

顺序表链表
优点

1.按下标随机访问 

2.CPU高速缓存命中率高

1.按需申请内存,不存在空间浪费

2.任意位置O(1)时间内插入删除数据

缺点

1.空间不够需增容(性能消耗及空间浪费)

2.头部或中间插入删除数据,需要挪动数据,效率低,时间复杂度O(n)

1.不支持下标随机访问

2.访问数据需要遍历,时间复杂度O(N)

顺序表和链表是相辅相成的,互相弥补对方的缺点,需要用顺序表还是链表存储数据,具体是看场景。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/806982
推荐阅读
相关标签
  

闽ICP备14008679号