深圳幻海软件技术有限公司 欢迎您!

【数据结构】链表

2023-09-05

单链表这张图是我们待会要实现的功能,我会尽可能的将每一步都说的很详细,方便理解。链表的概念及结构概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。这是较为书面的解释,这里我画个图解释一下:1的位置是当前链表的起始位置,我们称之为表头,它里面放

单链表

这张图是我们待会要实现的功能,我会尽可能的将每一步都说的很详细,方便理解。

链表的概念及结构
概念:链表是一种 物理存储结构上非连续 、非顺序的存储结构,数据元素的 逻辑顺序 是通过链表中的 指针链 次序实现的 。
这是较为书面的解释,这里我画个图解释一下:

1的位置是当前链表的起始位置,我们称之为表头,它里面放着的是第一个数据的地址(2),而2里面不仅有数据还有p2,p2指向的是第二个数据的位置,第二个数据里面不仅有2,同时里面放着p3,p3指向下一个数据的位置,而4这里最后的指针指向的是NULL,也就是说链表就到这里结束。

就像一条火车,里面的数据就是我们的车厢,而指针就是将数据链接起来的链条,所以我们称之为链表。

我们如何实现链表?

跟顺序表一样,我们要用到跟顺序表(我的专栏也写过结构体)类似的结构体,但是我们顺序表里面是这么写的:

我们拿一个指针指向一块连续的空间,然后有一个size记录当前有效数据,capicity记录容量,这样好处是我们可以通过下标来访问其中每一个数据,但缺点是当我们开辟数组的时候,第一个数组容量可能是10,然后我们放11个元素进去,这个时候空间不够,我们就重新开辟空间,一般我们开辟空间都是直接将原空间翻倍,但这里20个空间只放11个元素,空间就有些浪费了。

那我们链表是怎么写的呢?

同样,我们先来一个结构体,data就是我们除非囊的数据,而第二个参数是next,也就是下一个结构体的地址,当我们需要放11个数据的时候,顺序表我们可能直接创建了一整块数组然后往里面放,但链表则是一次放一个数据到里面,然后根据需求再开辟一空间放下一个数据。

尾插

因为打印需要先有内容,所以我们先冲尾插开始写:

尾插还没写完,我们就发现一个问题,就是当我们每次插入的时候都需要开辟一块空间,但开辟空间的代码又比较长,每次写起来会很麻烦,所以我们可以将其封装为一个函数 :

  1. SListNode* BuySLisenode(SLTDateType x)
  2. {
  3. SListNode*newnode = (SListNode*)malloc(sizeof(SListNode));
  4. if (newnode == NULL)
  5. {
  6. printf("malloc fail\n");
  7. }
  8. else
  9. {
  10. newnode->data = x;
  11. newnode->next = NULL;
  12. }
  13. return newnode;
  14. }

我们拿到要插入的数据,先计算空间大小,然后进行判断,如果指针为空指针我们就打印开括失败,如果成功,我们就将数据放到新开括空间的里面,然后将其下一个指针指向空,并返回这个这一块空间的地址。

 然后我们直接调用这个函数即可,尾插我们看代码分析:

  1. void SListPushBack(SListNode** pphead, SLTDateType x)
  2. {
  3. SListNode* newnode = BuySLisenode(x);
  4. if (*pphead == NULL)
  5. {
  6. *pphead = newnode;
  7. }
  8. else
  9. {
  10. SListNode*cur=*pphead;
  11. while (cur->next != NULL)
  12. {
  13. cur = cur->next;
  14. }
  15. cur->next = newnode;
  16. }
  17. }

尾插分两种情况,第一种,目前什么都没有,我们直接将新创建好的结构体地址给原本指针即可。

第二种,是目前里面已经有了结构体,我们就得找到数组的最后一个结构体,而最后的一个结构体它的指针肯定为空,然后将这个结构体的指针指向变成我们新创建的结构体地址。

为了看效果,我们再写一个打印函数,这个函数实现也很简单,我们进行判断,如果结构体指针不为空,我们就打印里面的内容,如果为空我们就停下:

头插

 既然实现了尾插,那我们再实现一个头插,相较于顺序表的头插还要移动数据,链表就简单一点看图:

原本我的1指向2,现在我中间插入一个5,我只需要将1指向的位置变成5,然后5的指针指向2即可:

 看代码:

  1. void SListPushFront(SListNode** pphead, SLTDateType x)
  2. {
  3. SListNode* newnode = BuySLisenode(x);
  4. if (*pphead == NULL)
  5. {
  6. *pphead = newnode;
  7. }
  8. else
  9. {
  10. newnode->next = *pphead;
  11. *pphead = newnode;
  12. }
  13. }

同样,我们先创建一个新的结构体,然后给它赋值,随后我们还是先判断当前链表有没有数据,没有就直接赋值,如果有,那就将我们新创建的结构体的下一个地址赋值为之前的第一个数据的地址,也就是我们传进来的那个地址解引用,然后我们再将原本的指向第一个元素的地址指向我们新创建的节点。

实现效果如下:

指定位置插入 

然后我们来一个难一点的,在指定位置插入,这里说难一点是因为我们要先找到指定的位置,然后再插入数据,但如果熟练适用以上两个插入,那这里其实也没那么难:

我原本是这样的结构,我现在要在2的位置插入一个结构,是不是要先找到2数据的位置,然后将它原本指向3的地址赋给我的新节点,然后将新节点的地址给它:

 然后我们就可以开始写代码了:

  1. void SeqListInsert(SListNode** pphead, size_t pos, SLTDateType x)
  2. {
  3. SListNode* newnode = BuySLisenode(x);
  4. if (*pphead == NULL)
  5. {
  6. *pphead = newnode;
  7. }
  8. else
  9. {
  10. SListNode*cur = *pphead;
  11. while (pos--)
  12. {
  13. if (cur == NULL)
  14. {
  15. printf("位置错误\n");
  16. }
  17. else
  18. {
  19. cur = cur->next;
  20. }
  21. }
  22. newnode->next = cur->next;
  23. cur->next = newnode;
  24. }
  25. }

这里是拿while循环来找的位置,实现的效果是这样的:

 ——————

解决了插入,那我们来解决一下删除

尾删

尾删就是将倒数第二个结构体的指针置为空指针

我们直接进行判断语句即可:

  1. void SListPopBack(SListNode** pphead)
  2. {
  3. if (*pphead == NULL)
  4. {
  5. printf("链表为空\n");
  6. }
  7. SListNode*cur = (*pphead)->next;
  8. SListNode*perv = *pphead;
  9. if (cur == NULL)
  10. {
  11. free(*pphead);
  12. *pphead = NULL;
  13. }
  14. else
  15. {
  16. while (cur->next)
  17. {
  18. if (cur->next != NULL)
  19. {
  20. cur = cur->next;
  21. perv = perv->next;
  22. }
  23. }
  24. perv->next = NULL;
  25. free(cur);
  26. }
  27. }

 同样,一进来我们先判断传进来的地址是否有内容,如果没有内容我们就可以直接打印链表为空,随后我们拿两个指针,第一个从第二个结构体开始,第二个从起始位置,因为我们要靠第一个来判断结构体指针是否为空,如果结构体不为空,我们就让第一个和第二个分别指向下一个结构体,如果第一个结构体走到指针为空的结构体,那第二个正好是它的前一个,我们将第二个的指针置为空,然后释放第一个指针的位置即可,演示效果:

头删

头删说来也很简单,原本传进来的地址放着的是第一个结构体的地址,我们可以直接将第二个结构体的位置传给起始地址即可:

代码实现如下:

  1. void SListPopFront(SListNode** pphead)
  2. {
  3. if (*pphead == NULL)
  4. {
  5. printf("链表为空\n");
  6. }
  7. else
  8. {
  9. SListNode*cur = (*pphead)->next;
  10. free(*pphead);
  11. *pphead = cur;
  12. }
  13. }

 我们直接拿到第二个结构体的地址,然后销毁第一个结构体,随后将地址重新赋值即可,看效果:

删除指定位置的值

这个实现其实就是一个复合,我们先找到位置,这里可以参考指定位置插入,然后删除结构体,删除我们也可以参考尾删,我们将其结合:

  1. void SListEraseAfter(SListNode**pphead, size_t pos)
  2. {
  3. if (*pphead == NULL)
  4. {
  5. printf("链表为空\n");
  6. }
  7. SListNode*cur = *pphead;
  8. if (pos)
  9. {
  10. while (pos--)
  11. {
  12. if (cur->next == NULL)
  13. {
  14. printf("位置错误\n");
  15. }
  16. else
  17. {
  18. cur = cur->next;
  19. }
  20. }
  21. SListNode*nex = cur->next;
  22. cur->next = nex->next;
  23. free(nex);
  24. }
  25. else
  26. {
  27. *pphead = (*pphead)->next;
  28. free(cur);
  29. }
  30. }

同样,一进来我们就可以判断是否为空指针,如果为空我们打印错误,如果不为空,我们就创建一个结构体变量来帮助我们找指定位置的数据,找到了数据之后我们在删除之前要先借助它找到它之后结构体的地址,所以我们才有

  1. SListNode*nex = cur->next;
  2. cur->next = nex->next;
  3. free(nex);

因为一旦你先删除,那这之后的所有结构体就再也找不到了,一定要先留下地址,再删除。

如果要删除0地址,那我们直接将第二个结构体的地址给地起始地址即可,别忘了释放创建的临时结构体。

查找

查找可能是最简单的,我们直接遍历,如果找到了数据我们就返回,没找到就返回-1,看代码:

  1. SListNode* SListFind(SListNode* phead, SLTDateType x)
  2. {
  3. if (phead == NULL)
  4. {
  5. printf("空链表\n");
  6. }
  7. SListNode* cur = phead;
  8. while (cur != NULL)
  9. {
  10. if (cur->data == x)
  11. {
  12. return cur;
  13. }
  14. else
  15. {
  16. cur = cur->next;
  17. }
  18. }
  19. return -1;
  20. }

销毁

  1. void SListDestory(SListNode* phead)
  2. {
  3. SListNode*cur = phead;
  4. SListNode*nex = phead;
  5. while (cur)
  6. {
  7. cur = cur->next;
  8. free(nex);
  9. nex = cur;
  10. }
  11. }

我们先创建两个结构体,第一个指向第二个数据,第二个指向第一个数据,我们先删除第一个数据,然后将第一个的值赋给第二个,再销毁第二个,循环到最后为空指针就可以结束了。

-------------------

这里放上所有代码,有需要可以直接复制:

SList.h

  1. #include<stdio.h>
  2. #include<assert.h>
  3. #include<stdlib.h>
  4. #define SLTDateType int
  5. typedef struct SListNode
  6. {
  7. int data;
  8. struct SListNode* next;
  9. } SListNode;
  10. void SListPrint(SListNode* phead); //打印
  11. void SListPushBack(SListNode** pphead, SLTDateType x); //尾插
  12. void SListPushFront(SListNode** pphead, SLTDateType x); //头插
  13. void SeqListInsert(SListNode** psl, size_t pos, SLTDateType x);//在指定位置插入
  14. void SListPopBack(SListNode** pphead);// 单链表的尾删
  15. void SListPopFront(SListNode** pphead);// 单链表头删
  16. void SListEraseAfter(SListNode**pphead, size_t pos);//删除pos之后的值
  17. SListNode* SListFind(SListNode* phead, SLTDateType x);//单链表的查找
  18. void SListDestory(SListNode* phead);//销毁链表

SList.c

  1. #include"SList.h"
  2. void SListPrint(SListNode* plist)
  3. {
  4. if (plist == NULL)
  5. {
  6. printf("单链表已空!\n");
  7. return;
  8. }
  9. SListNode*cur = plist;
  10. while (cur)
  11. {
  12. printf("%d -> ", cur->data);
  13. cur = cur->next;
  14. }
  15. printf("NULL\n");
  16. }
  17. SListNode* BuySLisenode(SLTDateType x)
  18. {
  19. SListNode*newnode = (SListNode*)malloc(sizeof(SListNode));
  20. if (newnode == NULL)
  21. {
  22. printf("malloc fail\n");
  23. }
  24. newnode->data = x;
  25. newnode->next = NULL;
  26. return newnode;
  27. }
  28. void SListPushBack(SListNode** pphead, SLTDateType x)
  29. {
  30. SLTDateType* newnode = BuySLisenode(x);
  31. if (*pphead == NULL)
  32. {
  33. *pphead = newnode;
  34. }
  35. else
  36. {
  37. SListNode*cur = *pphead;
  38. while (cur->next)
  39. {
  40. cur = cur->next;
  41. }
  42. cur->next = newnode;
  43. }
  44. }
  45. void SListPushFront(SListNode** pphead, SLTDateType x)
  46. {
  47. SListNode* newnode = BuySLisenode(x);
  48. if (*pphead == NULL)
  49. {
  50. *pphead = newnode;
  51. }
  52. else
  53. {
  54. newnode->next = *pphead;
  55. *pphead = newnode;
  56. }
  57. }
  58. void SeqListInsert(SListNode** pphead, size_t pos, SLTDateType x)
  59. {
  60. SListNode* newnode = BuySLisenode(x);
  61. if (*pphead == NULL)
  62. {
  63. *pphead = newnode;
  64. }
  65. else
  66. {
  67. SListNode*cur = *pphead;
  68. while (pos--)
  69. {
  70. if (cur == NULL)
  71. {
  72. printf("位置错误\n");
  73. }
  74. else
  75. {
  76. cur = cur->next;
  77. }
  78. }
  79. newnode->next = cur->next;
  80. cur->next = newnode;
  81. }
  82. }
  83. void SListPopBack(SListNode** pphead)
  84. {
  85. if (*pphead == NULL)
  86. {
  87. printf("链表为空\n");
  88. }
  89. SListNode*cur = (*pphead)->next;
  90. SListNode*perv = *pphead;
  91. if (cur == NULL)
  92. {
  93. free(*pphead);
  94. *pphead = NULL;
  95. }
  96. else
  97. {
  98. while (cur->next)
  99. {
  100. if (cur->next != NULL)
  101. {
  102. cur = cur->next;
  103. perv = perv->next;
  104. }
  105. }
  106. perv->next = NULL;
  107. free(cur);
  108. }
  109. }
  110. void SListPopFront(SListNode** pphead)
  111. {
  112. if (*pphead == NULL)
  113. {
  114. printf("链表为空\n");
  115. }
  116. else
  117. {
  118. SListNode*cur = (*pphead)->next;
  119. free(*pphead);
  120. *pphead = cur;
  121. }
  122. }
  123. void SListEraseAfter(SListNode**pphead, size_t pos)
  124. {
  125. if (*pphead == NULL)
  126. {
  127. printf("链表为空\n");
  128. }
  129. SListNode*cur = *pphead;
  130. if (pos)
  131. {
  132. while (pos--)
  133. {
  134. if (cur->next == NULL)
  135. {
  136. printf("位置错误\n");
  137. }
  138. else
  139. {
  140. cur = cur->next;
  141. }
  142. }
  143. SListNode*nex = cur->next;
  144. cur->next = nex->next;
  145. free(nex);
  146. }
  147. else
  148. {
  149. *pphead = (*pphead)->next;
  150. free(cur);
  151. }
  152. }
  153. SListNode* SListFind(SListNode* phead, SLTDateType x)
  154. {
  155. if (phead == NULL)
  156. {
  157. printf("空链表\n");
  158. }
  159. SListNode* cur = phead;
  160. while (cur != NULL)
  161. {
  162. if (cur->data == x)
  163. {
  164. return cur;
  165. }
  166. else
  167. {
  168. cur = cur->next;
  169. }
  170. }
  171. return -1;
  172. }
  173. void SListDestory(SListNode* phead)
  174. {
  175. SListNode*cur = phead;
  176. SListNode*nex = phead;
  177. while (cur)
  178. {
  179. cur = cur->next;
  180. free(nex);
  181. nex = cur;
  182. }
  183. }

主函数我就不放了,因为操作函数都在上面,也需要直接调用即可。

-----------

修改

更新,多一个修改函数,能将指定位置的值修改,原理就是找到位置然后修改,原理没变:

  1. void SListrevise(SListNode* phead, size_t pos, int x)
  2. {
  3. if (phead == NULL)
  4. {
  5. printf("链表为空\n");
  6. }
  7. else
  8. {
  9. SListNode*cur = phead;
  10. while (--pos)
  11. {
  12. if (cur == NULL)
  13. {
  14. printf("位置错误\n");
  15. }
  16. else
  17. {
  18. cur = cur->next;
  19. }
  20. }
  21. cur->data = x;
  22. }
  23. }

演示效果:

如果是pos--,那就会先运行一次再修改,也就是说位置会不一样,这里看图:

这里是pos--,可以看到修改的是第二个值

 同样的代码,这里是--pos,修改的是第一个值,注意一下就好

-------------

带头双向循环链表

1、如何开辟新的节点和初始化链表

2、双向链表的打印

3、双向链表尾插

4、双向链表尾删

5、双向链表头插

6、双向链表头删

7、双向链表查找

8、双向链表在pos的前面进行插入

9、双向链表删除pos位置的节点

10、双向链表的销毁

--------

事前准备:

我们需要一个创建一个结构体类型,并将里面的一些类型重定义名字,如下:

  1. typedef int LTDataType;
  2. typedef struct ListNode
  3. {
  4. LTDataType _data;
  5. struct ListNode* next;
  6. struct ListNode* prev;
  7. }ListNode;

我们还要知道双向带头循环链表是什么样的:

---------

如何开辟新的节点和初始化链表

我们知道,在链表中尾插,或者头插,你都必须先创建一个结构体,因为是双向带头循环的链表,所以这个链表必须有以下几条:

1、我们存放的内容

2、下一个结构体的地址

3、上一个结构体的地址

4、如果链表仅有表头,那么表头的下一个元素和上一个元素必须指向自己 

了解了条件,我们就来初始化我们链表:

  1. void ListInit(ListNode** pphead)
  2. {
  3. assert(pphead);
  4. *pphead = BuyLTNode(0);
  5. (*pphead)->next = *pphead;
  6. (*pphead)->prev = *pphead;
  7. }

 -----------

然后我们来看看如何创建新的节点:

跟单链表一样,我们开辟一块空间,将里面的值改成我们需要插入的值,然后再将其指针置为NULL,返回即可:

  1. ListNode* BuyListNode(int x)
  2. {
  3. ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  4. if (newnode == NULL)
  5. {
  6. printf("malloc false\n");
  7. exit(-1);
  8. }
  9. newnode->_data = x;
  10. newnode->prev = NULL;
  11. newnode->next = NULL;
  12. return newnode;
  13. }

------------

但是问题来,在初始化函数的时候,我们需不需要传入二级指针呢?

在单向不循环的链表时,我们是传入的链表的地址,因为如果我们要头插或者尾插,有可能会改变我们链表的表头位置

比方说:

ListNode* list = NULL;

尾插;

如果是单链表,这里list原本指向的空指针就应该被改成尾插的元素,但是如果我们传一级指针,

形参的改变不会影响形参,list出了尾插的函数它任然还是一个空指针,我们要改变的是链表头的指向的地址,所以我们传二级指针过去。

这里呢?

要改变什么,我们就传什么。

这里需不需要传二级指针过去呢?

我们会不会修改到这个头节点呢?

不会,我们只需要修改的是头节点里面指向下一个元素和上一个元素的地址,我们返回的还是头节点的位置,所以我们可以不用传二级指针过去。

所以我们可以将初始化函数这么写:

  1. ListNode* ListCreate()
  2. {
  3. ListNode* newnode = BuyListNode(0);
  4. newnode->prev = newnode;
  5. newnode->next = newnode;
  6. return newnode;
  7. }

我们直接创建一个节点,将里面的数据记为0即可。

------------

双向链表的打印

我们如何打印这个链表?

跟单链表一样,单链表我们什么时候停下来?

当遇到空,也就是说链表走完了一遍就可以停下来,那双向带头循环链表呢?

我们从表头的下一个元素走,比方说:

我们让一个指针cur开从表头往后走,每遇到一个值就打印,正因为循环,所以它到 5 的时候会回到表头,也就是 0 的位置,然后我们判断,如果 cur == 表头,那我们就可以停下来。

 

 我们的代码就这么写:

  1. void ListPrint(ListNode* pHead)
  2. {
  3. assert(pHead);
  4. ListNode* cur = pHead->next;
  5. while (cur!= pHead)
  6. {
  7. printf("%d->", cur->_data);
  8. cur = cur->next;
  9. }
  10. printf("end\n");
  11. }

----------

双向链表尾插

我们知道这是一个双向循环链表,我们尾插的时候还需要注意几点;

1、最后的结构体的下一个指针应该指向表头

2、表头的上一个结构体指针应该指向最后一个结构体

我们尾插的时候,先要找到没修改前的最后一个结构体,然后得到它的地址,再让它的下一个元素指向新的结构体地址,然后再将新结构体的上一个结构体指针指向它,再修改新节点的next,头节点的prev。

代码如下:

  1. void ListPushBack(ListNode* pHead, LTDataType x)
  2. {
  3. assert(pHead);
  4. ListNode* newnode = BuyListNode(x);
  5. ListNode* prev = pHead->prev;
  6. prev->next = newnode;
  7. newnode->prev = prev;
  8. newnode->next = pHead;
  9. pHead->prev = newnode;
  10. }

别忘了断言。

具体文字操作解释:

我们先让prev指向头节点的上一个元素,这里正是我们尾节点的位置,然后我们原本尾节点的下一个指针指向我们新节点,我们新节点的上一个指针指向我们原本的尾节点。

我们新节点的下一个指针应该指向表头,也就是pHead,而pHead表头的上一个结构体应该是尾节点,我们同样赋值过去。

----------------

双向链表尾删

单链表的尾删我们拿两个指针遍历数组找最后一个元素,双向链表就不需要遍历了,因为表头的上一个结构体正是我们的尾节点,我们拿一个指针记录尾戒点的上一个结构体,然后释放尾节点,再将尾节点释放,代码如下:

  1. void ListPopBack(ListNode* pHead)
  2. {
  3. assert(pHead);
  4. ListNode* prev = pHead->prev->prev;
  5. ListNode* cur = pHead->prev;
  6. if (pHead->next == cur)
  7. {
  8. printf("NULL");
  9. return;
  10. }
  11. else
  12. {
  13. prev->next = pHead;
  14. pHead->prev = prev;
  15. free(cur);
  16. cur = NULL;
  17. }
  18. }

具体文字解释:

两个指针,一个指向尾节点,一个指向尾节点的上一个结构体,因为靠尾节点我们才找得到倒数第二个。

先判断链表有没有值,没有就返回,有就继续,我们让倒数第二个节点的下一个指针指向我们的表头, next = pHead,再将表头的上一个结构体指针指向倒数第二个元素,也就是pHead->prev = prev,不需要返回值。

--------------

双向链表头插

就跟单链表的头插一样,只不过我们多加了个prev(链表的上一个结构体指针)而已,多赋一个值即可,但是要注意顺序,别死循环了。

  1. void ListPushFront(ListNode* pHead, LTDataType x)
  2. {
  3. assert(pHead);
  4. ListNode* newnode = BuyListNode(x);
  5. ListNode* next = pHead->next;
  6. pHead->next = newnode;
  7. newnode->prev = pHead;
  8. newnode->next = next;
  9. return;
  10. }

具体文字解释:

先创建一个指针指向原本第一个结构体,因为要在它之前插入新的结构体,所以地址先存一下

新结构体的下一个结构体指针指向原本的第一个结构体

表头的下一个结构体指针指向新开辟的节点

新开辟的节点的下一个结构体指针指向原本的第一个结构体

---------------

双向链表头删

既然你知道头插,也知道尾删,那头删就已经掌握了大半,看代码:

  1. void ListPopFront(ListNode* pHead)
  2. {
  3. assert(pHead);
  4. if (pHead->next == pHead)
  5. {
  6. printf("NULL\n");
  7. return;
  8. }
  9. ListNode* cur = pHead->next;
  10. ListNode* next = cur->next;
  11. free(cur);
  12. cur = NULL;
  13. pHead->next = next;
  14. next->prev = pHead;
  15. return;
  16. }

具体文字解释:

新创建的结构体指针cur指向原本的第一个结构体,

next指向第二个结构体

释放cur(第一个结构体)

头节点的下一个结构体指针指向next(原本的第二个元素)

next的上一个结构体指针指向头节点

--------------

双向链表查找

跟单链表一样,我们找链表里面的值判断,如果不是就继续走,如果走到了表头,我们就停下。看代码:

  1. ListNode* ListFind(ListNode* pHead, LTDataType x)
  2. {
  3. assert(pHead);
  4. if (pHead->next == pHead)
  5. {
  6. printf("NULL\n");
  7. return NULL;
  8. }
  9. ListNode* cur = pHead->next;
  10. while (cur != pHead)
  11. {
  12. if (cur->_data == x)
  13. {
  14. return cur;
  15. }
  16. cur = cur->next;
  17. }
  18. return NULL;
  19. }

具体文字解释:

新创建的节点cur指向表头的下一个结构体

遍历整个链表判断,如果cur不等于pHead,也就是说链表还有元素,就执行

我们比较cur里面的值,是就返回当前结构体的地址,不是就指向下一个结构体

如果遍历完还没找到,那就返回空指针

-------------

双向链表在pos的前面进行插入

创建新的节点,再将这个节点与之前的节点链接起来即可:

  1. void ListInsert(ListNode* pos, LTDataType x)
  2. {
  3. assert(pos);
  4. ListNode* prev = pos->prev;
  5. ListNode* newnode = BuyListNode(x);
  6. newnode->prev = prev;
  7. pos->prev = newnode;
  8. newnode->next = pos;
  9. prev->next = newnode;
  10. return;
  11. }

具体文字解释:

新创建的节点prev指向我们插入节点的前一个元素

我们先让新节点的上一个元素指向prev

再将插入节点位置的pos的上一个指针指向新节点

新节点的下一个指针指向pos

prev的下一个指针指向新节点

------------

删除指定节点

如果说上面的都能理解,那这是很简单的代码:

  1. void ListErase(ListNode* pos)
  2. {
  3. assert(pos);
  4. ListNode* prev = pos->prev;
  5. ListNode* next = pos->next;
  6. free(pos);
  7. pos = NULL;
  8. prev->next = next;
  9. next->prev = prev;
  10. return;
  11. }

文字详细解释:

prev指向删除节点的前一个元素

next指向删除节点的后一个元素

释放删除节点

链接prev和next

-------------

销毁链表

  1. void ListDestory(ListNode* pHead)
  2. {
  3. ListNode*cur = pHead;
  4. ListNode*nex = pHead;
  5. while (cur)
  6. {
  7. cur = cur->next;
  8. free(nex);
  9. nex = cur;
  10. }
  11. }

跟单链表一样,每次删除一个并保留下一个的地址,如果为空就删完了。

------------

这里放上所有代码,有需要可复制:

List.h

  1. #include<stdio.h>
  2. #include<assert.h>
  3. #include<stdlib.h>
  4. typedef int LTDataType;
  5. typedef struct ListNode
  6. {
  7. LTDataType _data;
  8. struct ListNode* next;
  9. struct ListNode* prev;
  10. }ListNode;
  11. //开辟一个新的节点并返回
  12. ListNode* BuyListNode(int x);
  13. // 创建返回链表的头结点.
  14. ListNode* ListCreate();
  15. // 双向链表销毁
  16. void ListDestory(ListNode* pHead);
  17. // 双向链表打印
  18. void ListPrint(ListNode* pHead);
  19. // 双向链表尾插
  20. void ListPushBack(ListNode* pHead, LTDataType x);
  21. // 双向链表尾删
  22. void ListPopBack(ListNode* pHead);
  23. // 双向链表头插
  24. void ListPushFront(ListNode* pHead, LTDataType x);
  25. // 双向链表头删
  26. void ListPopFront(ListNode* pHead);
  27. // 双向链表查找
  28. ListNode* ListFind(ListNode* pHead, LTDataType x);
  29. // 双向链表在pos的前面进行插入
  30. void ListInsert(ListNode* pos, LTDataType x);
  31. // 双向链表删除pos位置的节点
  32. void ListErase(ListNode* pos);

List.c

  1. #include"Lt.h"
  2. ListNode* BuyListNode(int x)
  3. {
  4. ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  5. if (newnode == NULL)
  6. {
  7. printf("malloc false\n");
  8. exit(-1);
  9. }
  10. newnode->_data = x;
  11. newnode->prev = NULL;
  12. newnode->next = NULL;
  13. return newnode;
  14. }
  15. ListNode* ListCreate()
  16. {
  17. ListNode* newnode = BuyListNode(0);
  18. newnode->prev = newnode;
  19. newnode->next = newnode;
  20. return newnode;
  21. }
  22. void ListPrint(ListNode* pHead)
  23. {
  24. assert(pHead);
  25. ListNode* cur = pHead->next;
  26. while (cur!= pHead)
  27. {
  28. printf("%d->", cur->_data);
  29. cur = cur->next;
  30. }
  31. printf("end\n");
  32. }
  33. void ListPushBack(ListNode* pHead, LTDataType x)
  34. {
  35. assert(pHead);
  36. ListNode* newnode = BuyListNode(x);
  37. ListNode* prev = pHead->prev;
  38. prev->next = newnode;
  39. newnode->prev = prev;
  40. newnode->next = pHead;
  41. pHead->prev = newnode;
  42. }
  43. void ListPopBack(ListNode* pHead)
  44. {
  45. assert(pHead);
  46. ListNode* prev = pHead->prev->prev;
  47. ListNode* cur = pHead->prev;
  48. if (pHead->next == cur)
  49. {
  50. printf("NULL");
  51. return;
  52. }
  53. else
  54. {
  55. prev->next = pHead;
  56. pHead->prev = prev;
  57. free(cur);
  58. cur = NULL;
  59. }
  60. }
  61. void ListPushFront(ListNode* pHead, LTDataType x)
  62. {
  63. assert(pHead);
  64. ListNode* newnode = BuyListNode(x);
  65. ListNode* next = pHead->next;
  66. pHead->next = newnode;
  67. newnode->prev = pHead;
  68. newnode->next = next;
  69. return;
  70. }
  71. void ListPopFront(ListNode* pHead)
  72. {
  73. assert(pHead);
  74. if (pHead->next == pHead)
  75. {
  76. printf("NULL\n");
  77. return;
  78. }
  79. ListNode* cur = pHead->next;
  80. ListNode* next = cur->next;
  81. free(cur);
  82. cur = NULL;
  83. pHead->next = next;
  84. next->prev = pHead;
  85. return;
  86. }
  87. ListNode* ListFind(ListNode* pHead, LTDataType x)
  88. {
  89. assert(pHead);
  90. if (pHead->next == pHead)
  91. {
  92. printf("NULL\n");
  93. return NULL;
  94. }
  95. ListNode* cur = pHead->next;
  96. while (cur != pHead)
  97. {
  98. if (cur->_data == x)
  99. {
  100. return cur;
  101. }
  102. cur = cur->next;
  103. }
  104. return NULL;
  105. }
  106. void ListInsert(ListNode* pos, LTDataType x)
  107. {
  108. assert(pos);
  109. ListNode* prev = pos->prev;
  110. ListNode* newnode = BuyListNode(x);
  111. newnode->prev = prev;
  112. pos->prev = newnode;
  113. newnode->next = pos;
  114. prev->next = newnode;
  115. return;
  116. }
  117. void ListErase(ListNode* pos)
  118. {
  119. assert(pos);
  120. ListNode* prev = pos->prev;
  121. ListNode* next = pos->next;
  122. free(pos);
  123. pos = NULL;
  124. prev->next = next;
  125. next->prev = prev;
  126. return;
  127. }
  128. void ListDestory(ListNode* pHead)
  129. {
  130. ListNode*cur = pHead;
  131. ListNode*nex = pHead;
  132. while (cur)
  133. {
  134. cur = cur->next;
  135. free(nex);
  136. nex = cur;
  137. }
  138. }

--------------- 

练手

简单题

1.构建单向链表并打印

牛牛的单向链表_牛客题霸_牛客网 (nowcoder.com)

非常简单,先创建数组接收数据,然后构建链表开始尾插,最后打印

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. typedef struct Node
  4. {
  5. int data;
  6. struct Node* next;
  7. }Node;
  8. //开辟节点并返回
  9. Node* BuyNode(int x)
  10. {
  11. Node* node = (Node*)malloc(sizeof(Node));
  12. node->data = x;
  13. node->next = NULL;
  14. return node;
  15. }
  16. //尾插
  17. void PushBack(Node** head, int x)
  18. {
  19. Node* node = BuyNode(x);
  20. Node* cur = *head;
  21. while (cur->next)
  22. cur = cur->next;
  23. cur->next = node;
  24. }
  25. //打印
  26. void NodePrintf(Node* head)
  27. {
  28. Node* cur = head;
  29. while (cur)
  30. {
  31. printf("%d ", cur->data);
  32. cur = cur->next;
  33. }
  34. }
  35. int main()
  36. {
  37. int n;
  38. scanf("%d", &n);
  39. int* a = (int*)malloc(sizeof(int)*n);
  40. for (int i = 0; i<n; i++)
  41. {
  42. scanf("%d", &a[i]);
  43. }
  44. Node* node = (Node*)malloc(sizeof(Node));
  45. //初始化...
  46. node->data = a[0];
  47. node->next = NULL;
  48. //依次尾插
  49. for (int i = 1; i < n; i++)
  50. {
  51. PushBack(&node, a[i]);
  52. }
  53. //打印
  54. NodePrintf(node);
  55. free(a);
  56. return 0;
  57. }

-------------

2.交换链表的首两个元素,尾两个元素

 代码几乎没变,上来判断有几个元素,如果有两个,就先交换,然后再找尾交换

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. typedef struct Node
  4. {
  5. int data;
  6. struct Node* next;
  7. }Node;
  8. Node* BuyNode(int x)
  9. {
  10. Node* node = (Node*)malloc(sizeof(Node));
  11. node->data = x;
  12. node->next = NULL;
  13. return node;
  14. }
  15. void PushBack(Node** head, int x)
  16. {
  17. Node* node = BuyNode(x);
  18. Node* cur = *head;
  19. while (cur->next)
  20. cur = cur->next;
  21. cur->next = node;
  22. }
  23. void NodePrintf(Node* head)
  24. {
  25. Node* cur = head;
  26. while (cur)
  27. {
  28. printf("%d ", cur->data);
  29. cur = cur->next;
  30. }
  31. }
  32. void Reverse(Node* head)
  33. {
  34. Node* cur = head;
  35. Node* prev = NULL;
  36. //如果cur->不为空,说明至少有两个元素,交换以下
  37. if(cur->next)
  38. {
  39. prev = cur;
  40. cur = cur->next;
  41. }
  42. int tmp = cur->data;
  43. cur->data = prev->data;
  44. prev->data = tmp;
  45. //如果cur->next为空,说明它就是最后一个元素
  46. while(cur->next)
  47. {
  48. prev = cur;
  49. cur = cur->next;
  50. }
  51. //交换
  52. tmp = cur->data;
  53. cur->data = prev->data;
  54. prev->data = tmp;
  55. }
  56. int main()
  57. {
  58. int n;
  59. scanf("%d", &n);
  60. int* a = (int*)malloc(sizeof(int)*n);
  61. for (int i = 0; i<n; i++)
  62. {
  63. scanf("%d", &a[i]);
  64. }
  65. Node* node = (Node*)malloc(sizeof(Node));
  66. node->data = a[0];
  67. node->next = NULL;
  68. for (int i = 1; i < n; i++)
  69. {
  70. PushBack(&node, a[i]);
  71. }
  72. Reverse(node);
  73. NodePrintf(node);
  74. free(a);
  75. return 0;
  76. }

-----------

3.单链表求和

代码几乎一样,遍历求和即可

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. typedef struct Node
  4. {
  5. int data;
  6. struct Node* next;
  7. }Node;
  8. Node* BuyNode(int x)
  9. {
  10. Node* node = (Node*)malloc(sizeof(Node));
  11. node->data = x;
  12. node->next = NULL;
  13. return node;
  14. }
  15. void PushBack(Node** head, int x)
  16. {
  17. Node* node = BuyNode(x);
  18. Node* cur = *head;
  19. while (cur->next)
  20. cur = cur->next;
  21. cur->next = node;
  22. }
  23. void NodePrintf(Node* head)
  24. {
  25. Node* cur = head;
  26. while (cur)
  27. {
  28. printf("%d ", cur->data);
  29. cur = cur->next;
  30. }
  31. }
  32. void Reverse(Node* head)
  33. {
  34. Node* cur = head;
  35. Node* prev = NULL;
  36. if(cur->next)
  37. {
  38. prev = cur;
  39. cur = cur->next;
  40. }
  41. int tmp = cur->data;
  42. cur->data = prev->data;
  43. prev->data = tmp;
  44. while(cur->next)
  45. {
  46. prev = cur;
  47. cur = cur->next;
  48. }
  49. tmp = cur->data;
  50. cur->data = prev->data;
  51. prev->data = tmp;
  52. }
  53. int main()
  54. {
  55. int n;
  56. scanf("%d", &n);
  57. int* a = (int*)malloc(sizeof(int)*n);
  58. for (int i = 0; i<n; i++)
  59. {
  60. scanf("%d", &a[i]);
  61. }
  62. Node* node = (Node*)malloc(sizeof(Node));
  63. node->data = a[0];
  64. node->next = NULL;
  65. for (int i = 1; i < n; i++)
  66. {
  67. PushBack(&node, a[i]);
  68. }
  69. int sum = 0;
  70. while(node)
  71. {
  72. sum+=node->data;
  73. node=node->next;
  74. }
  75. printf("%d ",sum);
  76. free(a);
  77. return 0;
  78. }

---------

4.双链表求和

先将数组里面加起来,然后插入节点遍历打印即可

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. typedef struct Node
  4. {
  5. int data;
  6. struct Node* next;
  7. }Node;
  8. Node* BuyNode(int x)
  9. {
  10. Node* node = (Node*)malloc(sizeof(Node));
  11. node->data = x;
  12. node->next = NULL;
  13. return node;
  14. }
  15. void PushBack(Node** head, int x)
  16. {
  17. Node* node = BuyNode(x);
  18. Node* cur = *head;
  19. while (cur->next)
  20. cur = cur->next;
  21. cur->next = node;
  22. }
  23. void NodePrintf(Node* head)
  24. {
  25. Node* cur = head;
  26. while (cur)
  27. {
  28. printf("%d ", cur->data);
  29. cur = cur->next;
  30. }
  31. }
  32. void Reverse(Node* head)
  33. {
  34. Node* cur = head;
  35. Node* prev = NULL;
  36. if(cur->next)
  37. {
  38. prev = cur;
  39. cur = cur->next;
  40. }
  41. int tmp = cur->data;
  42. cur->data = prev->data;
  43. prev->data = tmp;
  44. while(cur->next)
  45. {
  46. prev = cur;
  47. cur = cur->next;
  48. }
  49. tmp = cur->data;
  50. cur->data = prev->data;
  51. prev->data = tmp;
  52. }
  53. int main()
  54. {
  55. int n;
  56. scanf("%d", &n);
  57. int* a = (int*)malloc(sizeof(int)*n);
  58. int i;
  59. for (i = 0; i<n; i++)
  60. {
  61. scanf("%d", &a[i]);
  62. }
  63. for(i = 0;i<n;i++)
  64. {
  65. int tmp;
  66. scanf("%d",&tmp);
  67. a[i] += tmp;
  68. }
  69. Node* node = (Node*)malloc(sizeof(Node));
  70. node->data = a[0];
  71. node->next = NULL;
  72. for (int i = 1; i < n; i++)
  73. {
  74. PushBack(&node, a[i]);
  75. }
  76. NodePrintf(node);
  77. free(a);
  78. return 0;
  79. }

正式

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

203. 移除链表元素 - 力扣(LeetCode) (leetcode-cn.com)

这题有两种思路:

第一种,我们再开辟一个数组,如果原数组的值不等于我们要找的值,我们就开始链接下去,如果等于要找的值,我们就跳过找下一个元素,最后将最后一个元素的next = NULL。

这种思路较为简单,但是因为需要再开辟一个链表,所以它会有额外的空间消耗。

第二种,我们拿两个指针,一个快一步,一个慢一点,比如示例1中,我的快指针指向了6,而我的慢指针还指向的是2,那我们就直接让2的next指向3,然后释放6的空间,这样的好处就是不用在开辟空间,下面我们看代码实现:

第一种:

  1. struct ListNode* removeElements(struct ListNode* head, int val)
  2. {
  3. struct ListNode* newhead,*newtail;
  4. newhead = newtail = (struct ListNode*)malloc(sizeof(struct ListNode));
  5. struct ListNode* cur = head;
  6. while(cur)
  7. {
  8. if(cur->val != val)
  9. {
  10. newtail->next = cur;
  11. newtail = newtail->next;
  12. cur = cur->next;
  13. }
  14. else
  15. {
  16. cur = cur->next;
  17. }
  18. }
  19. newtail->next = NULL;
  20. return newhead->next;
  21. }

第二种:

  1. struct ListNode* removeElements(struct ListNode* head, int val)
  2. {
  3. struct ListNode* cur = head;
  4. struct ListNode* hhead = NULL;
  5. while(cur)
  6. {
  7. if(cur->val != val)
  8. {
  9. hhead= cur;
  10. cur= cur->next;
  11. }
  12. else
  13. {
  14. struct ListNode* next = cur->next;
  15. if(hhead == NULL)
  16. {
  17. free(cur);
  18. head = next;
  19. cur= next;
  20. }
  21. else
  22. {
  23. free(cur);
  24. hhead->next = next;
  25. cur=next;
  26. }
  27. }
  28. }
  29. return head;
  30. }

-------------

2. 反转一个单链表。

206. 反转链表 - 力扣(LeetCode) (leetcode-cn.com)

同样两种方法:

第一种,我们用三个指针,第一个指针指向空,第二个指针指向第一个指针,第三个指针指向第二个指针的下一个元素,以图举例,第一个指针为NULL,第二个指针是1,第三个指针是2,我们让1指向NULL,然后2指向1,然后我们将2的值给1,3的值给2,让它们指向下一个元素。

第二种,类似头插,我们让一个指针为NULL,然后我们让数组的第一个元素头插进去,第二个也头插进去,第三个也头插进去...返回最后一个头插的地址即可,看代码:

  1. struct ListNode* reverseList(struct ListNode* head)
  2. {
  3. struct ListNode* n1,*n2,*n3;
  4. if(head == NULL)
  5. {
  6. return NULL;
  7. }
  8. n1 = NULL;
  9. n2 = head;
  10. n3 = head->next;
  11. while(n2)
  12. {
  13. n2->next = n1;
  14. n1 = n2;
  15. n2 = n3;
  16. if(n3)
  17. {
  18. n3 = n3->next;
  19. }
  20. }
  21. return n1;
  22. }

第二种:

  1. struct ListNode* reverseList(struct ListNode* head)
  2. {
  3. struct ListNode* cur = head;
  4. struct ListNode* newhead =NULL;
  5. while(cur)
  6. {
  7. struct ListNode* next = cur->next;
  8. cur->next = newhead;
  9. newhead = cur;
  10. cur=cur->next;
  11. }
  12. return newhead;
  13. }

-----------

 3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个 中间结点。

876. 链表的中间结点 - 力扣(LeetCode) (leetcode-cn.com)

 同样,两种办法:

第一种,比较简单粗暴,我们先计算链表长度,然后除2,让头指针走这么多步再返回。

第二种,我们两个指针,一个快一个慢,快的一次走两步,慢的一次走一步,当快的指针走向尾部的时候慢的指针才走一半,我们返回慢的指针即可。

看代码实现:

第一种:

  1. struct ListNode* middleNode(struct ListNode* head)
  2. {
  3. int count = 0;
  4. struct ListNode* cur = head;
  5. while(cur)
  6. {
  7. count++;
  8. cur = cur->next;
  9. }
  10. count /= 2;
  11. cur = head;
  12. while(count--)
  13. {
  14. cur = cur->next;
  15. }
  16. return cur;
  17. }

第二种:

  1. struct ListNode* middleNode(struct ListNode* head)
  2. {
  3. struct ListNode* show = head;
  4. struct ListNode* fast = head;
  5. while(fast&&fast->next)
  6. {
  7. show = show->next;
  8. fast = fast->next->next;
  9. }
  10. return show;
  11. }

----------

4、输入一个链表,输出该链表中倒数第k个结点。

剑指 Offer 22. 链表中倒数第k个节点 - 力扣(LeetCode) (leetcode-cn.com)

 这题解法就比较单一,我们还是用双指针的思路,倒数第k个节点,我们就让快的指针先走k次,然后慢的指针再开始走,最后返回慢指针即可,看代码实现:

  1. struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
  2. {
  3. // write code here
  4. struct ListNode* cur = pListHead;
  5. while(k-- != 0)
  6. {
  7. if(cur == NULL)
  8. {
  9. return NULL;
  10. }
  11. cur=cur->next;
  12. }
  13. struct ListNode* prev = pListHead;
  14. while(cur)
  15. {
  16. cur = cur->next;
  17. prev = prev->next;
  18. }
  19. return prev;
  20. }

------------

5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

21. 合并两个有序链表 - 力扣(LeetCode) (leetcode-cn.com)

 如果常见有做过一道合并两个数组的题的话,那这里就可以用到其中的思路,就是我们先比较两个的值,把值小的先放进去,然后再让那个往下走一步,这里也可以用这种思路:

第一个指针指向list1 - 1,第二个指向list2 - 1,我们比较,一样大,我们就随便放一个进去,比如我这里放list2,然后list 2 = list2->next;然后我们在再比一次,如果其中一个为空了,我们就可以停下,然后判断,如果list1还有,我们就让最后的指针指向list1,如果list2还有,就指向list2,如果都没有,就置为空。

看代码实现:

  1. struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
  2. {
  3. struct ListNode cur = {0};
  4. struct ListNode* prev = &cur;
  5. while(list1&&list2)
  6. {
  7. if(list1->val>list2->val)
  8. {
  9. prev->next = list2;
  10. list2 = list2->next;
  11. }
  12. else
  13. {
  14. prev->next = list1;
  15. list1=list1->next;
  16. }
  17. prev=prev->next;
  18. }
  19. if(list1)
  20. {
  21. prev->next = list1;
  22. }
  23. else if(list2)
  24. {
  25. prev->next=list2;
  26. }
  27. else
  28. {
  29. prev->next = NULL;
  30. }
  31. return cur.next;
  32. }

----------

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

 链表分割_牛客题霸_牛客网 (nowcoder.com)

 这题就比较阴间,为什么这么说,不仅因为它不支持c,而且它还没有给图,全靠我们想,这里给大家画图理解一下:

这是它提供给我们的链表,我们要返回的是这样的:

 

 这里就说思路,我们拿两个指针开始遍历数组,第一个指针放的都是小于x的,第二个放的都是大于x的,然后我们再把它们连起来,具体看代码实现:

  1. class Partition {
  2. public:
  3. ListNode* partition(ListNode* pHead, int x) {
  4. // write code here
  5. ListNode*lessHead,*lessEnd,*greatHead,*greatEnd;
  6. lessHead = lessEnd = (ListNode*)malloc(sizeof(ListNode));
  7. greatHead = greatEnd = (ListNode*)malloc(sizeof(ListNode));
  8. lessEnd->next = greatEnd->next=NULL;
  9. ListNode*cur = pHead;
  10. while(cur)
  11. {
  12. if(cur->val < x)
  13. {
  14. lessEnd->next = cur;
  15. lessEnd = lessEnd->next;
  16. }
  17. else
  18. {
  19. greatEnd->next = cur;
  20. greatEnd = greatEnd->next;
  21. }
  22. cur = cur->next;
  23. }
  24. lessEnd->next = greatHead->next;
  25. greatEnd->next = NULL;
  26. ListNode*list = lessHead->next;
  27. free(lessHead);
  28. free(greatHead);
  29. return list;
  30. }
  31. };

上来我们开辟四个指针,其中两个是我们头,两个是我们的尾,我们需要尾去走,然后记住头来链接。

-----------

7. 链表的回文结构。

链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

两种办法:

第一种比较搓,我们开辟一个数组,数组里面是我们链表的val,然后比较数组的值再返回,因为靠开辟的是数组,所以实现起来相当简单,但是实际上我们是取巧了的,不建议适用这种办法,但是如果实在做不出来,还是可以用的,不管多搓,能做出来就算成功。

第二种,我们先找到链表中间的位置,然后我们再将链表后面的位置反转,然后再一个一个比较,因为反转链表我们之前写过,所以我直接拿来用了,具体实现看代码:

第一种:

  1. class PalindromeList {
  2. public:
  3. bool chkPalindrome(ListNode* A) {
  4. // write code here
  5. int arr[900];
  6. int count = 0;
  7. ListNode* cur = A;
  8. while(cur)
  9. {
  10. arr[count++] = cur->val;
  11. cur = cur->next;
  12. }
  13. int left=0,right=count-1;
  14. while(left<right)
  15. {
  16. if(arr[left]== arr[right])
  17. {
  18. left++;
  19. right--;
  20. }
  21. else
  22. {
  23. return false;
  24. }
  25. }
  26. return true;
  27. }
  28. };

第二种:

  1. class PalindromeList {
  2. public:
  3. bool chkPalindrome(ListNode* A) {
  4. // write code here
  5. ListNode* show,*fast;
  6. show = fast = A;
  7. while(fast&&fast->next)
  8. {
  9. show = show->next;
  10. fast = fast->next->next;
  11. }
  12. show = reverseList(show);
  13. ListNode* head = A;
  14. while(show&&show->next)
  15. {
  16. if(head->val == show->val)
  17. {
  18. head = head->next;
  19. show = show->next;
  20. }
  21. else
  22. {
  23. return false;
  24. }
  25. }
  26. return true;
  27. }
  28. };

这里得提一下,就是反转链表只能用三指针,因为你用第二种相当于在函数内部开辟了一个新节点返回节点的头,但出了函数节点头就找不到了,所以会报错,注意下就好。

---------

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

 160. 相交链表 - 力扣(LeetCode) (leetcode-cn.com)

两种思路:

第一种时间复杂度是O(n^2),大家看到这个时间复杂度就应该知道是什么办法了,就是拿a的每一个元素都跟b比,知道链表结束。

第二种:我们先看两个链表长度,我们让长的先走,这里我们看图,b很明显长一些,我们就让b先走,让它从b2开始比,然后同时往后走,碰到相交就停下返回,看代码实现:

第一种:

不推荐使用,看着简单,但链表一长太耗时

  1. struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
  2. {
  3. for(struct ListNode* a = headA;a!= NULL;a = a->next)
  4. {
  5. for(struct ListNode* b = headB;b!= NULL;b = b->next)
  6. {
  7. if(a == b)
  8. {
  9. return a;
  10. }
  11. }
  12. }
  13. return NULL;
  14. }

第二种:

  1. struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
  2. {
  3. struct ListNode* cura = headA;
  4. struct ListNode* curb = headB;
  5. int count=0,count1=0,count2=0;
  6. while(cura)
  7. {
  8. count1++;
  9. cura = cura->next;
  10. }
  11. while(curb)
  12. {
  13. count2++;
  14. curb=curb->next;
  15. }
  16. if(count1<count2)
  17. {
  18. curb = headA;
  19. cura = headB;
  20. }
  21. else
  22. {
  23. cura = headA;
  24. curb = headB;
  25. }
  26. count = abs(count1-count2);
  27. while(count--)
  28. {
  29. if(cura)
  30. {
  31. cura = cura->next;
  32. }
  33. }
  34. while(cura&&curb)
  35. {
  36. if(cura == curb)
  37. {
  38. return cura;
  39. }
  40. else
  41. {
  42. cura = cura->next;
  43. curb = curb->next;
  44. }
  45. }
  46. return NULL;
  47. }

----------

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

​​​​​​141. 环形链表 - 力扣(LeetCode) (leetcode-cn.com)

 很简单的一题,我们两个指针遍历链表,慢的一次一步,快的一次两步,什么时候慢的等于快的,就说明有环,如果没有环就直接返回,看代码:

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

----------

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

142. 环形链表 II - 力扣(LeetCode) (leetcode-cn.com)

这题更是重量级,这里说两种思路:

第一种,看图:

 这是我们已有的,我们拿两个快慢指针:

同时从这里开始走,当show和fast相遇的时候,它们肯定在环里面,我们假设是这里:

 

 我们让这个地方断开,让这里指向空,然后我们从下一个位置开始走,然后head开始走,当它们重合的时候,就是环的入口,看图;

 第二种:

 我们可以看到,show走的路程是L+X,而fast走的路程是 L + NC+C-X

N是圈数,简答来说,就是,当一个指针从fast和show相遇的地方走,另一个指针从head开始走,它们一定会相遇在环的入口处。

我推荐使用第一种,因为简单易懂,第二种理解起来比较麻烦,但如果问起,这也是一种解决办法。

看代码实现:

第一种:

  1. struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
  2. {
  3. struct ListNode* cura = headA;
  4. struct ListNode* curb = headB;
  5. int count=0,count1=0,count2=0;
  6. while(cura)
  7. {
  8. count1++;
  9. cura = cura->next;
  10. }
  11. while(curb)
  12. {
  13. count2++;
  14. curb=curb->next;
  15. }
  16. if(count1<count2)
  17. {
  18. curb = headA;
  19. cura = headB;
  20. }
  21. else
  22. {
  23. cura = headA;
  24. curb = headB;
  25. }
  26. count = abs(count1-count2);
  27. while(count--)
  28. {
  29. if(cura)
  30. {
  31. cura = cura->next;
  32. }
  33. }
  34. while(cura&&curb)
  35. {
  36. if(cura == curb)
  37. {
  38. return cura;
  39. }
  40. else
  41. {
  42. cura = cura->next;
  43. curb = curb->next;
  44. }
  45. }
  46. return NULL;
  47. }
  48. struct ListNode *detectCycle(struct ListNode* head)
  49. {
  50. struct ListNode* show,*fast;
  51. struct ListNode* cur = NULL;
  52. show = fast = head;
  53. while(fast &&fast->next)
  54. {
  55. show = show->next;
  56. fast = fast->next->next;
  57. if(fast == show)
  58. {
  59. cur = fast->next;
  60. fast->next = NULL;
  61. }
  62. }
  63. struct ListNode* prev = head;
  64. struct ListNode*pos = getIntersectionNode(prev,cur);
  65. if(pos)
  66. {
  67. return pos;
  68. }
  69. else
  70. return NULL;
  71. }

第二种:

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


11.两数相加 

2. 两数相加 - 力扣(LeetCode) (leetcode-cn.com)

先说思路,我们拿两个指针分别开始走这两个链表,再拿两个值记录这里面的值,再拿一个值记录进位,把这两个链表的值相加,如果这个值大于9,我们就放0,然后让计数+1,下一次计算相加的时候就加1,如果下一次的值不大于9,那加完就让计数变为0

  1. //插入节点函数
  2. struct ListNode* BuyNoude(int x)
  3. {
  4. struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
  5. newnode->val = x;
  6. newnode->next = NULL;
  7. return newnode;
  8. }
  9. struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2)
  10. {
  11. //两个指针开始对应两个链表
  12. struct ListNode* cur1 = l1;
  13. struct ListNode* cur2 = l2;
  14. //要返回的新链表
  15. struct ListNode* cur,*head;
  16. //计算进位
  17. int count = 0;
  18. //带哨兵位,其中一个往后走,另一个在头节点不变,等会就返回这个
  19. cur = head = (struct ListNode*)malloc(sizeof(struct ListNode));
  20. //判断循环条件,如果两个链表都为空才停下
  21. while(cur1 || cur2)
  22. {
  23. //计算里面的值,如果是空链表就算0
  24. int c1 = cur1==NULL?0:cur1->val;
  25. int c2 = cur2==NULL?0:cur2->val;
  26. //相加,count不进位情况为0,所以不影响
  27. int sum = c1+c2+count;
  28. //比较
  29. if(sum<10)
  30. {
  31. //尾插一个
  32. cur->next = BuyNoude(sum%10);
  33. cur = cur->next;
  34. //将计数变为0
  35. count = 0;
  36. }
  37. else
  38. {
  39. //如果大于9,我们就尾插一个0进去,表示进位,下一次计算就多加count/10,也就是1
  40. cur->next = BuyNoude(sum-10);
  41. cur = cur->next;
  42. count = sum/10;
  43. }
  44. //让两个链表往下走
  45. if(cur1!=NULL)//l1不为空,链接到下一节点
  46. cur1=cur1->next;
  47. if(cur2!=NULL)//l2不为空,链接到下一节点
  48. cur2=cur2->next;
  49. }
  50. //判断,此时count如果还有,就意味着没进位,所以再链接一下
  51. if(count != 0)
  52. {
  53. cur->next = BuyNoude(count);
  54. }
  55. return head->next;
  56. }

--------

希望以上的题能对你学习起到帮助,如果有兴趣可以点开我的专栏看更多,谢谢您的观看。

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览51117 人正在系统学习中