当前位置:   article > 正文

C语言数据结构之带头双向循环链表_c语言带头结点的双链表

c语言带头结点的双链表

 

目录

链表

双链表

头节点

带头双向循环链表的图

代码实现

双向链表的尾插

双向链表的尾删

双向链表的头插

双向链表的头删

 双向链表的查找

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

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

双向链表的销毁

关于链表的互用

完整代码


  • 链表

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

在现实中链表就像是火车每节车厢是相连,每节车厢都载着乘客或者货物。

链表也是一样,节点就像是车厢,它们都链接在一起,每个节点都有着数据。

  • 双链表

双链表就是每个节点有两个储存地址的结构体指针,一个存的是前一个节点的地址

一个存的是下一个节点的地址,用于更好的查找节点。

  • 头节点

头节点我们一般叫哨兵位,这个哨兵位我们不存什么数据,只存两个节点的地址。

命名:头 - phead  前一个节点 - prev   下一个节点 - next  要存的数据 - x  指定的位置 - pos

储存的数据 - data

上面是这个链表的一些命名。

  • 带头双向循环链表的图

 了解完这些我们再来实现下这个链表的增删查改

  • 代码实现

用结构体来存三个变量。用typedef取一个别名叫:LTNode使用,给一个内置类型int也取个别名叫:LTDataType方便更改。

  1. typedef int LTDataType;
  2. typedef struct ListNode
  3. {
  4. struct ListNode* next;
  5. struct ListNode* prev;
  6. LTDataType data;
  7. }LTNode;
  • 创建返回链表的头结点

链表的初始化就要申请空间来储存哨兵位了,哨兵位只存地址不存数据。因为要申请的节点空间很多所以,就单独写个函数来用。

这里的初始化因为有哨兵位,所以用返回值来返回。

头文件的声明

  1. //申请新的节点
  2. LTNode* BuyLTNode(LTDataType x);
  3. //创建返回链表的头结点
  4. LTNode* LTInit();

实现

  1. //申请新的节点
  2. LTNode* BuyLTNode(LTDataType x)
  3. {
  4. LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  5. if (newnode == NULL)
  6. {
  7. perror("malloc fail");
  8. return NULL;
  9. }
  10. newnode->next = NULL;
  11. newnode->prev = NULL;
  12. newnode->data = x;
  13. return newnode;
  14. }
  15. // 创建返回链表的头结点
  16. LTNode* LTInit()
  17. {
  18. LTNode* phead = BuyLTNode(-1);
  19. phead->next = phead;
  20. phead->prev = phead;
  21. return phead;
  22. }

 初始化的状态图

  • 双向链表的尾插

尾插建议定义一个tail来存头结点下一个的地址,这样就行链接的时候就可以随便连了。

tail是尾结点,newnode是新节点。如果不定义tail就靠着头结点的下一个(phead->next)来进行链接的话,我们链接的时候会很麻烦,需要分辨先后顺序,不然会找不到节点。
定义一个tail后,因为有下一个节点的地址了,所以我们可以随便进行链接,不需要分辨先后顺序。

插入后我们还可以打印一下插入的结果

声明

  1. //链表的尾插
  2. void LTPushBack(LTNode* phead, LTDataType x);

实现

  1. //双向链表的尾插
  2. void LTPushBack(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);
  5. LTNode* tail = phead->prev;
  6. LTNode* newnode = BuyLTNode(x);
  7. newnode->next = phead;
  8. newnode->prev = tail;
  9. tail->next = newnode;
  10. phead->prev = newnode;
  11. }

运用

  1. void LTTest1()
  2. {
  3. LTNode* plist = LTInit();
  4. LTPushBack(plist, 1);
  5. LTPushBack(plist, 2);
  6. LTPushBack(plist, 3);
  7. LTPushBack(plist, 4);
  8. LTPrint(plist);
  9. }
  10. int main()
  11. {
  12. LTTest1();
  13. return 0;
  14. }

 打印结果

 

  • 双向链表的尾删

链表的尾删就很简单了,定义一个tail(尾)用来删除,然后找到前一个,叫tailprev。这样就记录了,两个节点。再把tail,free掉。这个时候tail被释放了,所以tailprev的next没有地址,需要存头结点phead的地址。phead的prev存一下tailprev的地址。所有的链接就完成了。

但是尾删删到最后把头结点删掉,我们在进行插入就会有问题。所以我们需要用assert进行断言一下,以防我们删到最后出现问题。

这里的断言的判断就写一个函数来返回真假。当LTEmpty判断phead的next等于phead的时候返回真,然后我们再取反一下,就可以assert判断就为假,为假就触发断言结束程序咯。

声明

  1. //双链表的尾删
  2. void LTPopBak(LTNode* phead);
  3. //判空
  4. bool LTEmpty(LTNode* phead);

实现

  1. //判空
  2. bool LTEmpty(LTNode* phead)
  3. {
  4. assert(phead);
  5. return phead->next == phead;
  6. }
  7. //双链表的尾删
  8. void LTPopBak(LTNode* phead)
  9. {
  10. assert(phead);
  11. assert(!LTEmpty(phead));
  12. LTNode* tail = phead->prev;
  13. LTNode* tailPrev = tail->prev;
  14. free(tail);
  15. tailPrev->next = phead;
  16. phead->prev = tailPrev;
  17. }

运用

  1. void LTTest1()
  2. {
  3. LTNode* plist = LTInit();
  4. LTPushBack(plist, 1);
  5. LTPushBack(plist, 2);
  6. LTPushBack(plist, 3);
  7. LTPushBack(plist, 4);
  8. LTPrint(plist);
  9. LTPopBak(plist);
  10. LTPrint(plist);
  11. LTPopBak(plist);
  12. LTPrint(plist);
  13. LTPopBak(plist);
  14. LTPrint(plist);
  15. LTPopBak(plist);
  16. LTPrint(plist);
  17. }
  18. int main()
  19. {
  20. LTTest1();
  21. return 0;
  22. }

打印结果

 

 当然删完后断言就起作用了,可以看见是函数里出的问题,然后就可以顺藤摸瓜去找了。

 

  • 双向链表的头插

链表的头插和尾插没有太大区别,用newnode来存新节点,然后next存头结点的下一个的地址,方便查找。

后面的就是和尾插一样的链接咯。因为存的头结点后面的地址,所以可以随便链接。

声明

  1. //链表的头插
  2. void LTPushFront(LTNode* phead, LTDataType x);

实现

  1. //链表的头插
  2. void LTPushFront(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);
  5. LTNode* newnode = BuyLTNode(x);
  6. LTNode* next = phead->next;
  7. phead->next = newnode;
  8. newnode->prev = phead;
  9. newnode->next = next;
  10. next->prev = newnode;
  11. }

运用

  1. void LTTest2()
  2. {
  3. LTNode* plist = LTInit();
  4. LTPushFront(plist, 1);
  5. LTPushFront(plist, 2);
  6. LTPushFront(plist, 3);
  7. LTPushFront(plist, 4);
  8. LTPrint(plist);
  9. }
  10. int main()
  11. {
  12. LTTest2();
  13. return 0;
  14. }

打印结果

  • 双向链表的头删

 链表的头删和尾删大同小异,first存phead的next的地址,second存first的next的地址。

因为要删的是first,所以phead的next存second的地址,second存prev存phead的地址。

声明

  1. //链表的头删
  2. void LTPopFront(LTNode* phead);

实现

  1. //链表的头删
  2. void LTPopFront(LTNode* phead)
  3. {
  4. assert(phead);
  5. assert(!LTEmpty(phead));
  6. LTNode* first = phead->next;
  7. LTNode* second = first->next;
  8. phead->next = second;
  9. second->prev = phead;
  10. free(first);
  11. }

运用

这里是多删了一个,所以会被断言报错

  1. void LTTest2()
  2. {
  3. LTNode* plist = LTInit();
  4. LTPushFront(plist, 1);
  5. LTPushFront(plist, 2);
  6. LTPushFront(plist, 3);
  7. LTPushFront(plist, 4);
  8. LTPrint(plist);
  9. LTPopFront(plist);
  10. LTPrint(plist);
  11. LTPopFront(plist);
  12. LTPrint(plist);
  13. LTPopFront(plist);
  14. LTPrint(plist);
  15. LTPopFront(plist);
  16. LTPrint(plist);
  17. LTPrint(plist);
  18. }
  19. int main()
  20. {
  21. LTTest2();
  22. return 0;
  23. }

结果打印

  •  双向链表的查找

这个链表的查找需要配合,pos位置的插入使用,这个就是把链表来跑一遍来找值,找到了就返回,节点的地址。

声明

  1. // 双向链表查找
  2. LTNode* LTFind(LTNode* phead, LTDataType x);

实现

  1. // 双向链表查找
  2. LTNode* LTFind(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);
  5. LTNode* cur = phead->next;
  6. while (cur != phead)
  7. {
  8. if (cur->data == x)
  9. {
  10. return cur;
  11. }
  12. cur = cur->next;
  13. }
  14. return NULL;
  15. }
  • 双向链表在pos位置的前面进行插入

pos位置前插入和头插,尾插没有什么区别。

运用这里用pos来存一下LTFind返回的地址,然后判断一下pos是不是为空,不是空就进行插入。

声明

  1. // 双向链表在pos的前面进行插入
  2. void LTInsert(LTNode* pos, LTDataType x);

实现

  1. // 双向链表在pos的前面进行插入
  2. void LTInsert(LTNode* pos, LTDataType x)
  3. {
  4. assert(pos);
  5. LTNode* newnode = BuyLTNode(x);
  6. LTNode* posPrev = pos->prev;
  7. newnode->next = pos;
  8. newnode->prev = posPrev;
  9. posPrev->next = newnode;
  10. pos->prev = newnode;
  11. }

运用

  1. void LTTest3()
  2. {
  3. LTNode* plist = LTInit();
  4. LTPushBack(plist, 1);
  5. LTPushBack(plist, 2);
  6. LTPushBack(plist, 3);
  7. LTPushBack(plist, 4);
  8. LTPrint(plist);
  9. LTNode* pos = LTFind(plist, 2);
  10. if (pos)
  11. {
  12. LTInsert(pos, 20);
  13. }
  14. LTPrint(plist);
  15. }
  16. int main()
  17. {
  18. LTTest3();
  19. return 0;
  20. }

打印

 

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

删除pos位置和尾删头删没什么区别就不写了。

声明

  1. // 双向链表删除pos位置的结点
  2. void LTErase(LTNode* pos);

实现

  1. // 双向链表删除pos位置的结点
  2. void LTErase(LTNode* pos)
  3. {
  4. assert(pos);
  5. assert(!LTEmpty(pos));
  6. LTNode* posPrev = pos->prev;
  7. LTNode* posNext = pos->next;
  8. posPrev->next = posNext;
  9. posNext->prev = posPrev;
  10. free(pos);
  11. }

运用

  1. void LTTest3()
  2. {
  3. LTNode* plist = LTInit();
  4. LTPushBack(plist, 1);
  5. LTPushBack(plist, 2);
  6. LTPushBack(plist, 3);
  7. LTPushBack(plist, 4);
  8. LTPrint(plist);
  9. LTNode* pos = LTFind(plist, 2);
  10. if (pos)
  11. {
  12. LTInsert(pos, 20);
  13. }
  14. LTPrint(plist);
  15. LTErase(pos);
  16. LTPrint(plist);
  17. }
  18. int main()
  19. {
  20. LTTest3();
  21. return 0;
  22. }

打印

 

  • 双向链表的销毁

销毁就是把链表所以的节点free删掉。用个循环来删除节点,删完后吧哨兵位(头结点)删除就好了。使用后顺便把plist也置空一下。

声明

  1. // 双向链表销毁
  2. void LTDestroy(LTNode* phead);

实现

  1. // 双向链表销毁
  2. void LTDestroy(LTNode* phead)
  3. {
  4. assert(phead);
  5. LTNode* cur = phead->next;
  6. while (cur != phead)
  7. {
  8. LTNode* next = cur->next;
  9. free(cur);
  10. cur = next;
  11. }
  12. free(phead);
  13. }

运用

  1. void LTTest3()
  2. {
  3. LTNode* plist = LTInit();
  4. LTPushBack(plist, 1);
  5. LTPushBack(plist, 2);
  6. LTPushBack(plist, 3);
  7. LTPushBack(plist, 4);
  8. LTPrint(plist);
  9. LTNode* pos = LTFind(plist, 2);
  10. if (pos)
  11. {
  12. LTInsert(pos, 20);
  13. }
  14. LTPrint(plist);
  15. LTErase(pos);
  16. LTPrint(plist);
  17. LTDestroy(plist);
  18. plist = NULL;
  19. }
  20. int main()
  21. {
  22. LTTest3();
  23. return 0;
  24. }

打印

  • 关于链表的互用

在上边可以看见pos前插入,删除的pos位置。和头插尾插,头删尾删没有太大区别。

这样看来我们就可以直接用pos前插入的函数来实现头插尾插。删除pos位置,来实现头删尾删。

上边该实现的都实现了,下面就直接给实现的代码了。

  • 尾插的互用

要尾删的节点就在phead的prev位置。pos前插入就给phead位置就行了。函数会自己找到

  1. //双向链表的尾插
  2. void LTPushBack(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);
  5. LTInsert(phead, x);
  6. }
  • 尾删的互用

尾删的位置就是phead的前一个

  1. //双链表的尾删
  2. void LTPopBak(LTNode* phead)
  3. {
  4. assert(phead);
  5. assert(!LTEmpty(phead));
  6. LTErase(phead->prev);
  7. }
  • 头插的互用

头插的位置就是phead的下一个位置,把值传过去就好了

  1. //链表的头插
  2. void LTPushFront(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);
  5. LTInsert(phead->next, x);
  6. }
  • 头删的互用

头删的位置就是phead的下一个

  1. //链表的头删
  2. void LTPopFront(LTNode* phead)
  3. {
  4. assert(phead);
  5. assert(!LTEmpty(phead));
  6. LTErase(phead->next);
  7. }
  • 完整代码

我用的文件有3个有头文件,函数的实现文件,主函数的文件。

  • 头文件List.h
  1. #pragma once
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <assert.h>
  5. #include <stdbool.h>
  6. typedef int LTDataType;
  7. typedef struct ListNode
  8. {
  9. struct ListNode* prev;
  10. struct ListNode* next;
  11. LTDataType data;
  12. }LTNode;
  13. //申请新的节点
  14. LTNode* BuyLTNode(LTDataType x);
  15. //链表初始化
  16. LTNode* LTInit();
  17. //链表的尾插
  18. void LTPushBack(LTNode* phead, LTDataType x);
  19. //双链表的尾删
  20. void LTPopBak(LTNode* phead);
  21. //判空
  22. bool LTEmpty(LTNode* phead);
  23. //链表的头插
  24. void LTPushFront(LTNode* phead, LTDataType x);
  25. //链表的头删
  26. void LTPopFront(LTNode* phead);
  27. // 双向链表查找
  28. LTNode* LTFind(LTNode* phead, LTDataType x);
  29. // 双向链表在pos的前面进行插入
  30. void LTInsert(LTNode* pos, LTDataType x);
  31. // 双向链表删除pos位置的结点
  32. void LTErase(LTNode* pos);
  33. // 双向链表销毁
  34. void LTDestroy(LTNode* phead);
  • 函数实现的文件List.c
  1. #include "List.h"
  2. //申请新的节点
  3. LTNode* BuyLTNode(LTDataType x)
  4. {
  5. LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  6. if (newnode == NULL)
  7. {
  8. perror("malloc fail");
  9. return NULL;
  10. }
  11. newnode->next = NULL;
  12. newnode->prev = NULL;
  13. newnode->data = x;
  14. return newnode;
  15. }
  16. //打印
  17. void LTPrint(LTNode* phead)
  18. {
  19. assert(phead);
  20. LTNode* cur = phead->next;
  21. while (cur != phead)
  22. {
  23. printf("%d ", cur->data);
  24. cur = cur->next;
  25. }
  26. printf("\n");
  27. }
  28. //双向链表初始化
  29. LTNode* LTInit()
  30. {
  31. LTNode* phead = BuyLTNode(-1);
  32. phead->next = phead;
  33. phead->prev = phead;
  34. return phead;
  35. }
  36. //双向链表的尾插
  37. void LTPushBack(LTNode* phead, LTDataType x)
  38. {
  39. assert(phead);
  40. LTInsert(phead, x);
  41. //LTNode* tail = phead->prev;
  42. //LTNode* newnode = BuyLTNode(x);
  43. //newnode->next = phead;
  44. //newnode->prev = tail;
  45. //tail->next = newnode;
  46. //phead->prev = newnode;
  47. }
  48. //双链表的尾删
  49. void LTPopBak(LTNode* phead)
  50. {
  51. assert(phead);
  52. assert(!LTEmpty(phead));
  53. LTErase(phead->prev);
  54. //LTNode* tail = phead->prev;
  55. //LTNode* tailPrev = tail->prev;
  56. //free(tail);
  57. //tailPrev->next = phead;
  58. //phead->prev = tailPrev;
  59. }
  60. //链表的头插
  61. void LTPushFront(LTNode* phead, LTDataType x)
  62. {
  63. assert(phead);
  64. LTInsert(phead->next, x);
  65. //LTNode* newnode = BuyLTNode(x);
  66. //LTNode* next = phead->next;
  67. //phead->next = newnode;
  68. //newnode->prev = phead;
  69. //newnode->next = next;
  70. //next->prev = newnode;
  71. }
  72. //链表的头删
  73. void LTPopFront(LTNode* phead)
  74. {
  75. assert(phead);
  76. assert(!LTEmpty(phead));
  77. LTErase(phead->next);
  78. //LTNode* first = phead->next;
  79. //LTNode* second = first->next;
  80. //phead->next = second;
  81. //second->prev = phead;
  82. //free(first);
  83. }
  84. // 双向链表查找
  85. LTNode* LTFind(LTNode* phead, LTDataType x)
  86. {
  87. assert(phead);
  88. LTNode* cur = phead->next;
  89. while (cur != phead)
  90. {
  91. if (cur->data == x)
  92. {
  93. return cur;
  94. }
  95. cur = cur->next;
  96. }
  97. return NULL;
  98. }
  99. // 双向链表在pos的前面进行插入
  100. void LTInsert(LTNode* pos, LTDataType x)
  101. {
  102. assert(pos);
  103. LTNode* newnode = BuyLTNode(x);
  104. LTNode* posPrev = pos->prev;
  105. newnode->next = pos;
  106. newnode->prev = posPrev;
  107. posPrev->next = newnode;
  108. pos->prev = newnode;
  109. }
  110. // 双向链表删除pos位置的结点
  111. void LTErase(LTNode* pos)
  112. {
  113. assert(pos);
  114. assert(!LTEmpty(pos));
  115. LTNode* posPrev = pos->prev;
  116. LTNode* posNext = pos->next;
  117. posPrev->next = posNext;
  118. posNext->prev = posPrev;
  119. free(pos);
  120. }
  121. //判空
  122. bool LTEmpty(LTNode* phead)
  123. {
  124. assert(phead);
  125. return phead->next == phead;
  126. }
  127. // 双向链表销毁
  128. void LTDestroy(LTNode* phead)
  129. {
  130. assert(phead);
  131. LTNode* cur = phead->next;
  132. while (cur != phead)
  133. {
  134. LTNode* next = cur->next;
  135. free(cur);
  136. cur = next;
  137. }
  138. free(phead);
  139. }
  • 主函数文件Test.c

这里有我对所有函数的使用测试。

  1. #include "List.h"
  2. void LTTest1()
  3. {
  4. LTNode* plist = LTInit();
  5. LTPushBack(plist, 1);
  6. LTPushBack(plist, 2);
  7. LTPushBack(plist, 3);
  8. LTPushBack(plist, 4);
  9. LTPrint(plist);
  10. LTPopBak(plist);
  11. LTPrint(plist);
  12. LTPopBak(plist);
  13. LTPrint(plist);
  14. LTPopBak(plist);
  15. LTPrint(plist);
  16. LTPopBak(plist);
  17. LTPrint(plist);
  18. //LTPopBak(plist);
  19. //LTPrint(plist);
  20. }
  21. void LTTest2()
  22. {
  23. LTNode* plist = LTInit();
  24. LTPushFront(plist, 1);
  25. LTPushFront(plist, 2);
  26. LTPushFront(plist, 3);
  27. LTPushFront(plist, 4);
  28. LTPrint(plist);
  29. LTPopFront(plist);
  30. LTPrint(plist);
  31. LTPopFront(plist);
  32. LTPrint(plist);
  33. LTPopFront(plist);
  34. LTPrint(plist);
  35. LTPopFront(plist);
  36. LTPrint(plist);
  37. //LTPopFront(plist);
  38. //LTPrint(plist);
  39. }
  40. void LTTest3()
  41. {
  42. LTNode* plist = LTInit();
  43. LTPushBack(plist, 1);
  44. LTPushBack(plist, 2);
  45. LTPushBack(plist, 3);
  46. LTPushBack(plist, 4);
  47. LTPrint(plist);
  48. LTNode* pos = LTFind(plist, 2);
  49. if (pos)
  50. {
  51. LTInsert(pos, 20);
  52. }
  53. LTPrint(plist);
  54. LTErase(pos);
  55. LTPrint(plist);
  56. LTDestroy(plist);
  57. plist = NULL;
  58. }
  59. int main()
  60. {
  61. LTTest3();
  62. return 0;
  63. }

最后谢谢观看,如果那里写得不对的地方,欢迎提出,探讨。白白

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

闽ICP备14008679号