当前位置:   article > 正文

数据结构(初阶)—— C语言实现双向带头循环链表_c双向链表带头尾

c双向链表带头尾

 

目录

一、链表种类的优劣

二、什么是双向循环链表 

三,双向循环链表各接口函数实现 

1.双链表的初始化

2.双链表的打印 

3.扩容函数 

4.双链表的尾插 

5.双链表的尾删

6.双链表的头插

7.双链表的头删 

8.双链表的查找

9. 双链表在pos位置之前插入结点

10.双链表删除pos位置的结点 

11.双链表的销毁 

四、源代码 

List.h

List.c

test.c

五、顺序表和双向链表的总结

1.顺序表的优点与缺点

2. 双向链表的优点与缺点


一、链表种类的优劣

链表可分为8种:

单向双向
单向带头循环双向带头循环
单向带头不循环双向带头不循环
单向不带头循环双向不带头循环
单向不带头不循环双向不带头不循环

在C语言实现链表那篇博客中https://blog.csdn.net/sjsjnsjnn/article/details/123920224?spm=1001.2014.3001.5501

主要实现的是单向不带头非循环的链表结构;

此结构:

        结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
-------------------------------------------------------------------------------------------------------------------------
本次主要分析 双向带头循环链表的链表结构;
此结构:
         结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向 循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而 简单了;

二、什么是双向循环链表 

     双向循环链表和单链表都是由结点组成的,单链表包含一个数据域和一个指针域构成,而双向循环链表不同,它是由一个数据域和两个指针域组成,其中指针包含前驱指针(prev)和后继指针(next);

三,双向循环链表各接口函数实现 

1.双链表的初始化

  1. //双向带头循环链表的初始化
  2. LTNode* ListInit()
  3. {
  4. LTNode* phead = (LTNode*)malloc(sizeof(LTNode));//创建头结点
  5. phead->next = phead;//后继指针指向头
  6. phead->prev = phead;//前驱指针指向头
  7. return phead;
  8. }

2.双链表的打印 

  1. //双向带头循环链表的打印
  2. void ListPrint(LTNode* phead)
  3. {
  4. assert(phead);
  5. LTNode* cur = phead->next;
  6. while (cur != phead)
  7. {
  8. printf("%d->", cur->Data);
  9. cur = cur->next;
  10. }
  11. printf("\n");
  12. }

3.扩容函数 

  1. //增容函数
  2. LTNode* BuyListNode(LTDataType x)
  3. {
  4. LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  5. if (newnode == NULL)
  6. {
  7. printf("malloc fail\n");
  8. exit(-1);
  9. }
  10. newnode->Data = x;
  11. newnode->next = NULL;
  12. newnode->prev = NULL;
  13. return newnode;
  14. }

4.双链表的尾插 

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

5.双链表的尾删

  1. //双向带头循环链表的尾删
  2. void ListPopBack(LTNode* phead)
  3. {
  4. assert(phead);
  5. assert(phead->next != phead);
  6. LTNode* tail = phead->prev;
  7. LTNode* tailprev = tail->prev;
  8. free(tail);
  9. tailprev->next = phead;
  10. phead->prev = tailprev;
  11. //ListErase(phead->prev);//尾删就相当于复用Erase这个函数
  12. }

 

6.双链表的头插

  1. //双向带头循环链表的头插
  2. void ListPushFront(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);
  5. LTNode* newnode = BuyListNode(x);
  6. LTNode* next = phead->next;//先找到头
  7. phead->next = newnode;
  8. newnode->prev = phead;
  9. newnode->next = next;
  10. next->prev = newnode;
  11. //ListInsert(phead->next, x);
  12. }

 

7.双链表的头删 

  1. //双向带头循环链表的头删
  2. void ListPopFront(LTNode* phead)
  3. {
  4. assert(phead);
  5. assert(phead->next != phead);//如果哨兵位的后继指针指向的是头,就不能去调用头删
  6. LTNode* next = phead->next;//先找到头结点
  7. LTNode* nextNext = next->next;//再找到头结点的下一个结点
  8. phead->next = next->next;
  9. nextNext->prev = phead;
  10. }

 

8.双链表的查找

  1. //双向带头循环链表的查找
  2. LTNode* ListFind(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);
  5. LTNode* cur = phead->next;//从头结点出发
  6. while (cur != phead)
  7. {
  8. //找到返回对应的地址
  9. if (cur->Data == x)
  10. {
  11. return cur;
  12. }
  13. //找不到继续向后找
  14. cur = cur->next;
  15. }
  16. //彻底找不到
  17. return NULL;
  18. }

9. 双链表在pos位置之前插入结点

  1. //双向带头循环链表pos位置之前插入
  2. void ListInsert(LTNode* pos, LTDataType x)
  3. {
  4. assert(pos);
  5. LTNode* posPrev = pos->prev;//先找到pos的前一个结点的位置
  6. LTNode* newnode = BuyListNode(x);
  7. posPrev->next = newnode;
  8. newnode->prev = posPrev;
  9. newnode->next = pos;
  10. pos->prev = newnode;
  11. }

10.双链表删除pos位置的结点 

  1. //双向带头循环链表pos位置删除
  2. void ListErase(LTNode* pos)
  3. {
  4. assert(pos);
  5. LTNode* posPrev = pos->prev;//找到pos的前一个位置
  6. LTNode* posNext = pos->next;//和pos的后一个位置
  7. //把前一个结点和后一个结点链接起来
  8. posPrev->next = posNext;
  9. posNext->prev = posPrev;
  10. //释放pos结点
  11. free(pos);
  12. pos = NULL;
  13. }

11.双链表的销毁 

  1. //双向带头循环链表的销毁
  2. void ListDestroy(LTNode* phead)
  3. {
  4. //在销毁链表的时候,逐个销毁,销毁前一个,必须要保存下一个结点的地址
  5. assert(phead);
  6. LTNode* cur = phead->next;
  7. while (cur != phead)
  8. {
  9. LTNode* next = cur->next;
  10. free(cur);
  11. cur = next;
  12. }
  13. free(phead);
  14. phead = NULL;
  15. }

四、源代码 

List.h

  1. #pragma once
  2. #include <stdio.h>
  3. #include <assert.h>
  4. #include <stdlib.h>
  5. typedef int LTDataType;
  6. typedef struct ListNode
  7. {
  8. LTDataType Data;
  9. struct ListNode* next;
  10. struct ListNode* prev;
  11. }LTNode;
  12. //双向带头循环链表的初始化
  13. LTNode* ListInit();
  14. //双向带头循环链表的打印
  15. void ListPrint(LTNode* phead);
  16. //增容函数
  17. LTNode* BuyListNode(LTDataType x);
  18. //双向带头循环链表的尾插
  19. void ListPushBack(LTNode* phead, LTDataType x);
  20. //双向带头循环链表的尾删
  21. void ListPopBack(LTNode* phead);
  22. //双向带头循环链表的头插
  23. void ListPushFront(LTNode* phead, LTDataType x);
  24. //双向带头循环链表的头删
  25. void ListPopFront(LTNode* phead);
  26. //双向带头循环链表的查找
  27. LTNode* ListFind(LTNode* phead, LTDataType x);
  28. //双向带头循环链表pos位置之前插入
  29. void ListInsert(LTNode* pos, LTDataType x);
  30. //双向带头循环链表pos位置删除
  31. void ListErase(LTNode* pos);
  32. //双向带头循环链表的销毁
  33. void ListDestroy(LTNode* phead);

List.c

  1. #include "List.h"
  2. //双向带头循环链表的初始化
  3. LTNode* ListInit()
  4. {
  5. LTNode* phead = (LTNode*)malloc(sizeof(LTNode));//创建头结点
  6. phead->next = phead;//后继指针指向头
  7. phead->prev = phead;//前驱指针指向头
  8. return phead;
  9. }
  10. //双向带头循环链表的打印
  11. void ListPrint(LTNode* phead)
  12. {
  13. assert(phead);
  14. LTNode* cur = phead->next;
  15. while (cur != phead)
  16. {
  17. printf("%d->", cur->Data);
  18. cur = cur->next;
  19. }
  20. printf("\n");
  21. }
  22. //增容函数
  23. LTNode* BuyListNode(LTDataType x)
  24. {
  25. LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  26. if (newnode == NULL)
  27. {
  28. printf("malloc fail\n");
  29. exit(-1);
  30. }
  31. newnode->Data = x;
  32. newnode->next = NULL;
  33. newnode->prev = NULL;
  34. return newnode;
  35. }
  36. //双向带头循环链表的尾插
  37. void ListPushBack(LTNode* phead, LTDataType x)
  38. {
  39. assert(phead);
  40. LTNode* tail = phead->prev;
  41. LTNode* newnode = BuyListNode(x);
  42. tail->next = newnode;
  43. newnode->prev = tail;
  44. newnode->next = phead;
  45. phead->prev = newnode;
  46. }
  47. //双向带头循环链表的尾删
  48. void ListPopBack(LTNode* phead)
  49. {
  50. assert(phead);
  51. assert(phead->next != phead);
  52. LTNode* tail = phead->prev;
  53. LTNode* tailprev = tail->prev;
  54. free(tail);
  55. tailprev->next = phead;
  56. phead->prev = tailprev;
  57. //ListErase(phead->prev);//尾删就相当于复用Erase这个函数
  58. }
  59. //双向带头循环链表的头插
  60. void ListPushFront(LTNode* phead, LTDataType x)
  61. {
  62. assert(phead);
  63. LTNode* newnode = BuyListNode(x);
  64. LTNode* next = phead->next;
  65. phead->next = newnode;
  66. newnode->prev = phead;
  67. newnode->next = next;
  68. next->prev = newnode;
  69. //ListInsert(phead->next, x);
  70. }
  71. //双向带头循环链表的头删
  72. void ListPopFront(LTNode* phead)
  73. {
  74. assert(phead);
  75. assert(phead->next != phead);
  76. LTNode* next = phead->next;
  77. LTNode* nextNext = next->next;
  78. phead->next = next->next;
  79. nextNext->prev = phead;
  80. }
  81. //双向带头循环链表的查找
  82. LTNode* ListFind(LTNode* phead, LTDataType x)
  83. {
  84. assert(phead);
  85. LTNode* cur = phead->next;
  86. while (cur != phead)
  87. {
  88. if (cur->Data == x)
  89. {
  90. return cur;
  91. }
  92. cur = cur->next;
  93. }
  94. return NULL;
  95. }
  96. //双向带头循环链表pos位置之前插入
  97. void ListInsert(LTNode* pos, LTDataType x)
  98. {
  99. assert(pos);
  100. LTNode* posPrev = pos->prev;
  101. LTNode* newnode = BuyListNode(x);
  102. posPrev->next = newnode;
  103. newnode->prev = posPrev;
  104. newnode->next = pos;
  105. pos->prev = newnode;
  106. }
  107. //双向带头循环链表pos位置删除
  108. void ListErase(LTNode* pos)
  109. {
  110. assert(pos);
  111. LTNode* posPrev = pos->prev;
  112. LTNode* posNext = pos->next;
  113. posPrev->next = posNext;
  114. posNext->prev = posPrev;
  115. free(pos);
  116. pos = NULL;
  117. }
  118. //双向带头循环链表的销毁
  119. void ListDestroy(LTNode* phead)
  120. {
  121. assert(phead);
  122. LTNode* cur = phead->next;
  123. while (cur != phead)
  124. {
  125. LTNode* next = cur->next;
  126. free(cur);
  127. cur = next;
  128. }
  129. free(phead);
  130. phead = NULL;
  131. }

test.c

  1. #include "List.h"
  2. void TestList1()
  3. {
  4. LTNode* plist = ListInit();
  5. ListPushBack(plist, 5);
  6. ListPushBack(plist, 6);
  7. ListPushBack(plist, 7);
  8. ListPushBack(plist, 8);
  9. ListPrint(plist);
  10. ListPopBack(plist);
  11. ListPrint(plist);
  12. ListPushFront(plist, 4);
  13. ListPushFront(plist, 3);
  14. ListPushFront(plist, 2);
  15. ListPushFront(plist, 1);
  16. ListPrint(plist);
  17. //ListPopFront(plist);
  18. //ListPopFront(plist);
  19. //ListPrint(plist);
  20. LTNode* ret = ListFind(plist, 4);
  21. ListInsert(ret, 30);
  22. ListPrint(plist);
  23. ListDestroy(plist);
  24. plist = NULL;
  25. }
  26. int main()
  27. {
  28. TestList1();
  29. return 0;
  30. }

五、顺序表和双向链表的总结

1.顺序表的优点与缺点

优点:(可以使用下标访问 )     

1.支持随机访问;需要随机访问结构支持算法可以很好的适用;

        2.cpu高速缓存命中率更高 

缺点:

        1.头部、中部插入删除数据时间效率低。O(N)

        2.连续的物理空间,空间不够需要增容;

                ①、增容有一定程度消耗

                ②、为了避免频繁增容,一般我们都按倍数去增,用不完可能存在一定的空间浪费

2. 双向链表的优点与缺点

优点:(不可以使用下标访问 )

        1.任意位置插入,效率高;o(1)

        2.按需申请释放空间;

缺点:

        1.不支持随机访问;意味着:一些排序,二分查找在这种结构上不适用;

        2.链表存储一个值,同时要存储链接指针,也有一定的消耗;

        3.cpu高速缓存命中率更低;

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

闽ICP备14008679号