当前位置:   article > 正文

双向链表(带头双向循环链表)的实现

双向链表(带头双向循环链表)的实现

前言:前面实现的单向链表,全称是不带头单向不循环链表。这里实现带头双向不循环链表,比单向链表好实现一点。

目录

链表的分类

单向链表与双向链表的比较:

 双向链表的节点的定义:

多文件实现:

List.h来看一下需要实现的函数接口:

List.c的各个函数的实现:

创建节点的函数:

链表的初始化:

尾插:

头插:

打印链表:

尾删: 

头删:

查找

插入(在指定位置之后插入):

删除指定位置的数据:

销毁链表:

实现双链表的全部源码:

List.h:

List.c:

test.c的主要功能是测试个接口,所以这里的代码仅供参考哦!

结语:


链表的分类

将是否带头,单向还是双向,是否循环,随机组合一共有八种类型,只要会写不带头单向不循环链表和带头双向循环链表这两个链表,其他的链表实现起来就简单多了。

单向链表与双向链表的比较:

单向链表双向链表
是否带头不带头带头(有哨兵位)
单向还是双向单向双向
是否循环不循环循环

逻辑图解比较:

 双向链表的节点的定义:

  1. typedef int LTTypeData;
  2. typedef struct ListNode
  3. {
  4. LTTypeData data;
  5. struct ListNode* prev;
  6. struct ListNode* next;
  7. }LTNode;

prev指向前一个节点

next指向下一个节点

data存储数据

为什么tydef一个新的数据类型是为了方便以后修改存储的数据类型

多文件实现:

文件名称负责的功能
List.h链表的定义,需要用到库里的头文件的包含,链表需要实现函数的声明。
List.c链表函数的实现
test.c测试接口(这里大家可以自己测,自己写)

这里的建议是每写一个接口就测试一个接口,省得全写完在测试,有一堆麻烦,还没开始解决麻烦,就先把自己的心态搞崩了。

List.h来看一下需要实现的函数接口:

  1. #pragma once
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <assert.h>
  5. typedef int LTTypeData;
  6. typedef struct ListNode
  7. {
  8. LTTypeData data;
  9. struct ListNode* prev;
  10. struct ListNode* next;
  11. }LTNode;
  12. //初始化链表
  13. void LTInite(LTNode** pphead);
  14. //尾插
  15. void LTPushBack(LTNode* phead, LTTypeData x);
  16. //头插
  17. void LTPushFront(LTNode* phead, LTTypeData x);
  18. //头删
  19. void LTPopFront(LTNode* phead);
  20. //尾删
  21. void LTPopBack(LTNode* phead);
  22. //打印链表中的数据
  23. void LTPrint(LTNode* phead);
  24. //查找
  25. LTNode* LTFind(LTNode* phead, LTTypeData x);
  26. //在指定位置之后插入
  27. void LTInsert(LTNode* pos, LTTypeData x);
  28. //删除指定位置的数据
  29. void LTErase(LTNode* pos);
  30. //销毁链表
  31. void LTDestroy(LTNode* phead);

List.c的各个函数的实现:

因为会频繁的创建新的节点,这里直接将创建新的节点分装成一个函数。

创建节点的函数:

  1. LTNode* LTBuyNode(LTTypeData x)
  2. {
  3. LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  4. if (newnode == NULL)
  5. {
  6. perror("malloc");
  7. exit(1);
  8. }
  9. newnode->data = x;
  10. newnode->next = newnode;
  11. newnode->prev = newnode;
  12. return newnode;
  13. }

注意这里创建出的节点的next和prev指针都指向自己。

图解:

链表的初始化:

  1. void LTInite(LTNode** pphead)
  2. {
  3. assert(pphead);//链表不能无效
  4. *pphead = LTBuyNode(-1);
  5. }

这里*pphead指向的节点为头结点(哨兵位),哨兵位的数据为无用的数据。

尾插:

  1. void LTPushBack(LTNode* phead, LTTypeData x)
  2. {
  3. assert(phead);
  4. LTNode* newnode = LTBuyNode(x);
  5. newnode->prev = phead->prev;
  6. newnode->next = phead;
  7. phead->prev->next = newnode;
  8. phead->prev = newnode;
  9. }

为什么参数是一级指针而不是二级指针?

在单链表时尾插需要传二级指针是因为需要改变指针变量phead的值;而在双向链表中有哨兵位,改变的是哨兵位的prev和next指针的值,所以不需要传二级指针。

注意修改顺序:先改newnode的prev和next,再改原链表的尾节点的next(指向新的节点)和头节点的prev(指向新的节点)

 图解:

注意这里2和3不能调换顺序,如果调换顺序,那么prev->next就找不到原来链表的尾节点了。

头插:

  1. void LTPushFront(LTNode* phead, LTTypeData x)
  2. {
  3. assert(phead);//防止链表无效
  4. LTNode* newnode = LTBuyNode(x);
  5. newnode->next = phead->next;
  6. newnode->prev = phead;
  7. phead->next->prev = newnode;
  8. phead->next = newnode;
  9. }

 注意:还是顺序问题:最后两句不能调换顺序,否则,phead->next就指向的不是原链表的第一个节点,就找不到原链表的第一个节点了。

打印链表:

  1. void LTPrint(LTNode* phead)
  2. {
  3. assert(phead);//防止链表无效
  4. LTNode* pcur = phead->next;
  5. while (pcur != phead)
  6. {
  7. printf("%d->", pcur->data);
  8. pcur = pcur->next;
  9. }
  10. printf("\n");
  11. }

这里注意循环结束的标志:

当pcur指向哨兵位时,循环结束。

看一下打印效果:

  1. #include "List.h"
  2. void test1()
  3. {
  4. LTNode* plist = NULL;
  5. LTInite(&plist);
  6. LTPushBack(plist, 1);
  7. LTPrint(plist);
  8. LTPushBack(plist, 2);
  9. LTPrint(plist);
  10. LTPushBack(plist, 3);
  11. LTPrint(plist);
  12. LTPushFront(plist, 4);
  13. LTPrint(plist);
  14. LTPopFront(plist);
  15. LTPrint(plist);
  16. }
  17. int main()
  18. {
  19. test1();
  20. }

效果展示: 

尾删: 

  1. void LTPopBack(LTNode* phead)
  2. {
  3. //链表不能为空,不能无效
  4. assert(phead && phead->next != NULL);
  5. LTNode* del = phead->prev;
  6. del->prev->next = phead;
  7. phead->prev = del->prev;
  8. free(del);
  9. del = NULL;
  10. }

这里需要修改的链表的地方:尾节点的前一个节点的next和头节点的prev

步骤图解:

注意:这里需要保存下尾节点的地址,防止到最后释放的时候找不到。

头删:

  1. void LTPopFront(LTNode* phead)
  2. {
  3. assert(phead && phead->next != NULL);
  4. LTNode* del = phead->next;
  5. del->next->prev = del->prev;
  6. phead->next = del->next;
  7. free(del);
  8. del = NULL;
  9. }

 跟尾删一样,需要注意的一点就是,需要创建一个新的变量(del)存储需要删除的节点的地址。

查找:

  1. LTNode* LTFind(LTNode* phead, LTTypeData x)
  2. {
  3. assert(phead);
  4. LTNode* pcur = phead->next;
  5. while (pcur != phead)
  6. {
  7. if (pcur->data == x)
  8. {
  9. return pcur;
  10. }
  11. pcur = pcur->next;
  12. }
  13. return NULL;
  14. }

注意下循环结束的条件:跟上面的打印函数一样,需要遍历一遍链表。

返回值:如果没找到返回NULL,找到了就返回那个节点的地址。

插入(在指定位置之后插入):

  1. void LTInsert(LTNode* pos, LTTypeData x)
  2. {
  3. assert(pos);
  4. LTNode* newnode = LTBuyNode(x);
  5. newnode->next = pos->next;
  6. newnode->prev = pos;
  7. pos->next->prev = newnode;
  8. pos->next = newnode;
  9. }

注意:最后两句的顺序不能交换。这里的原因上面的头插尾插的原因一样,若交换就找不到pos指向的节点的下一个节点。

删除指定位置的数据:

  1. void LTErase(LTNode* pos)
  2. {
  3. assert(pos);
  4. pos->prev->next = pos->next;
  5. pos->next->prev = pos->prev;
  6. free(pos);
  7. pos = NULL;
  8. }

这里没啥好注意的 。

销毁链表:

  1. void LTDestroy(LTNode* phead)
  2. {
  3. assert(phead);
  4. LTNode* pcur = phead->next;
  5. LTNode* next = phead->next;
  6. while (pcur!=phead)
  7. {
  8. next = pcur->next;
  9. free(pcur);
  10. pcur = next;
  11. }
  12. free(phead);
  13. phead = NULL;
  14. }

注意:这里用到前后指针:防止释放完一个节点找不到之后的节点。

最后哨兵位释放,因为这里传的是一级指针不会修改test.c 文件中的phead的值,所以调用完该函数后,需要手动的将test.c中的phNULL.

那么,这里为什么不传二级指针呢?

保证接口的一致性,所以传的二级指针。

实现双链表的全部源码:

List.h:

  1. #pragma once
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <assert.h>
  5. typedef int LTTypeData;
  6. typedef struct ListNode
  7. {
  8. LTTypeData data;
  9. struct ListNode* prev;
  10. struct ListNode* next;
  11. }LTNode;
  12. //初始化链表
  13. void LTInite(LTNode** pphead);
  14. //尾插
  15. void LTPushBack(LTNode* phead, LTTypeData x);
  16. //头插
  17. void LTPushFront(LTNode* phead, LTTypeData x);
  18. //头删
  19. void LTPopFront(LTNode* phead);
  20. //尾删
  21. void LTPopBack(LTNode* phead);
  22. //打印链表中的数据
  23. void LTPrint(LTNode* phead);
  24. //查找
  25. LTNode* LTFind(LTNode* phead, LTTypeData x);
  26. //在指定位置之后插入
  27. void LTInsert(LTNode* pos, LTTypeData x);
  28. //删除指定位置的数据
  29. void LTErase(LTNode* pos);
  30. //销毁链表
  31. void LTDestroy(LTNode* phead);

List.c:

  1. #include "List.h"
  2. LTNode* LTBuyNode(LTTypeData x)
  3. {
  4. LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  5. if (newnode == NULL)
  6. {
  7. perror("malloc");
  8. exit(1);
  9. }
  10. newnode->data = x;
  11. newnode->next = newnode;
  12. newnode->prev = newnode;
  13. return newnode;
  14. }
  15. void LTInite(LTNode** pphead)
  16. {
  17. assert(pphead);
  18. *pphead = LTBuyNode(-1);
  19. }
  20. void LTPushBack(LTNode* phead, LTTypeData x)
  21. {
  22. assert(phead);
  23. LTNode* newnode = LTBuyNode(x);
  24. newnode->prev = phead->prev;
  25. newnode->next = phead;
  26. phead->prev->next = newnode;
  27. phead->prev = newnode;
  28. }
  29. void LTPrint(LTNode* phead)
  30. {
  31. assert(phead);
  32. LTNode* pcur = phead->next;
  33. while (pcur != phead)
  34. {
  35. printf("%d->", pcur->data);
  36. pcur = pcur->next;
  37. }
  38. printf("\n");
  39. }
  40. void LTPushFront(LTNode* phead, LTTypeData x)
  41. {
  42. assert(phead);
  43. LTNode* newnode = LTBuyNode(x);
  44. newnode->next = phead->next;
  45. newnode->prev = phead;
  46. phead->next->prev = newnode;
  47. phead->next = newnode;
  48. }
  49. void LTPopFront(LTNode* phead)
  50. {
  51. assert(phead && phead->next != NULL);
  52. LTNode* del = phead->next;
  53. del->next->prev = del->prev;
  54. phead->next = del->next;
  55. free(del);
  56. del = NULL;
  57. }
  58. void LTPopBack(LTNode* phead)
  59. {
  60. assert(phead && phead->next != NULL);
  61. LTNode* del = phead->prev;
  62. del->prev->next = phead;
  63. phead->prev = del->prev;
  64. free(del);
  65. del = NULL;
  66. }
  67. LTNode* LTFind(LTNode* phead, LTTypeData x)
  68. {
  69. assert(phead);
  70. LTNode* pcur = phead->next;
  71. while (pcur != phead)
  72. {
  73. if (pcur->data == x)
  74. {
  75. return pcur;
  76. }
  77. pcur = pcur->next;
  78. }
  79. return NULL;
  80. }
  81. void LTInsert(LTNode* pos, LTTypeData x)
  82. {
  83. assert(pos);
  84. LTNode* newnode = LTBuyNode(x);
  85. newnode->next = pos->next;
  86. newnode->prev = pos;
  87. pos->next->prev = newnode;
  88. pos->next = newnode;
  89. }
  90. void LTErase(LTNode* pos)
  91. {
  92. assert(pos);
  93. pos->prev->next = pos->next;
  94. pos->next->prev = pos->prev;
  95. free(pos);
  96. pos = NULL;
  97. }
  98. void LTDestroy(LTNode* phead)
  99. {
  100. assert(phead);
  101. LTNode* pcur = phead->next;
  102. LTNode* next = phead->next;
  103. while (pcur!=phead)
  104. {
  105. next = pcur->next;
  106. free(pcur);
  107. pcur = next;
  108. }
  109. free(phead);
  110. phead = NULL;
  111. }

test.c的主要功能是测试个接口,所以这里的代码仅供参考哦!

  1. #include "List.h"
  2. void test1()
  3. {
  4. LTNode* plist = NULL;
  5. LTInite(&plist);
  6. LTPushBack(plist, 1);
  7. LTPrint(plist);
  8. LTPushBack(plist, 2);
  9. LTPrint(plist);
  10. LTPushBack(plist, 3);
  11. LTPrint(plist);
  12. LTPushFront(plist, 4);
  13. LTPrint(plist);
  14. LTPopFront(plist);
  15. LTPrint(plist);
  16. //LTPopBack(plist);
  17. //LTPrint(plist);
  18. //LTNode* find = LTFind(plist, 4);
  19. //if (find == NULL)
  20. // printf("没找到\n");
  21. //else
  22. // printf("找到了\n");
  23. //LTInsert(find, 5);
  24. //LTPrint(plist);
  25. //LTErase(find);
  26. //LTPrint(plist);
  27. //LTDestroy(plist);
  28. //plist = NULL;
  29. }
  30. int main()
  31. {
  32. test1();
  33. return 0;
  34. }

结语:

继单链表后的又一个数据结构的实现。

cheers!

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号