当前位置:   article > 正文

【数据结构】C语言实现双链表_用c语言构造一个包含5个元素的双链表

用c语言构造一个包含5个元素的双链表

目录

前言

双链表节点定义

接口函数实现

初始化函数

创建节点

打印双链表

尾插节点

尾删节点

 头插节点

 头删节点

 指定位置前插入

 删除指定位置节点

改写插入删除 

判断链表是否为空

计算链表长度

销毁链表

双链表完整代码

浅谈链表及顺序表


前言

前面我们已经实现了无头单向非循环链表,现在我们来实现带头双向循环链表。

双链表节点定义

  1. //双链表节点定义
  2. typedef int LDataType;
  3. typedef struct ListNode
  4. {
  5. struct ListNode* prev;
  6. struct ListNode* next;
  7. LDataType data;
  8. }LNode;

与单链表不同的是,在实现双链表时,我们需要创建一个初始化函数。双链表的初始化需要一个头节点,并且这个头节点的prev指针和next指针需要指向自身。

接口函数实现

初始化函数

初始化函数创建了一个头节点(哨兵卫),这个哨兵卫不储存值,并且让它的prev、next指向自身。这样就形成了一个闭环。

  1. //初始化双链表
  2. LNode* LInit()
  3. {
  4. LNode* newnode = (LNode*)malloc(sizeof(LNode));
  5. if (newnode == NULL)
  6. {
  7. perror("malloc fail");
  8. exit(-1);
  9. }
  10. //prev与next均指向自身
  11. newnode->next = newnode;
  12. newnode->prev = newnode;
  13. return newnode;
  14. }

 与单链表的实现类似,我们同样也实现两个函数用来检测和辅助其他函数的实现。

创建节点

  1. //创建节点、
  2. LNode* BuyListNode(LDataType x)
  3. {
  4. LNode* newnode = (LNode*)malloc(sizeof(LNode));
  5. if (newnode == NULL)
  6. {
  7. perror("malloc fail");
  8. exit(-1);
  9. }
  10. newnode->data = x;
  11. //创建后的节点prev与next均指向NULL
  12. newnode->next = NULL;
  13. newnode->prev = NULL;
  14. return newnode;
  15. }

新创建的节点的prev与next指针我们让他们指向NULL就可以了,因为后面我们还会处理它的链接关系,所以指向空指针就可以了。 

打印双链表

  1. //打印双链表
  2. void LPrint(LNode* phead)
  3. {
  4. assert(phead);
  5. LNode* cur = phead->next;
  6. while (cur != phead)
  7. {
  8. printf("%d ", cur->data);
  9. cur = cur->next;
  10. }
  11. printf("\n");
  12. }

PS:

        打印链表信息之所以从phead->next开始是因为我们的phead并没有存值,链表的节点信息是从phead->next开始的。

尾插节点

  1. //尾插节点
  2. void LPushBack(LNode* phead,LDataType x)
  3. {
  4. //防止传入空指针
  5. assert(phead);
  6. //先将x存入创建的节点中
  7. LNode* newnode = BuyListNode(x);
  8. //链表最后一个节点就是phead的prev指针
  9. LNode* tail = phead->prev;
  10. //将newnode节点链接到链表中
  11. tail->next = newnode;
  12. phead->prev = newnode;
  13. newnode->next = phead;
  14. newnode->prev = tail;
  15. }

        可能会有人会问,为什么双链表在尾插的时候传的参数是一级指针啊?单链表在尾插节点的的时候传的就是二级指针。那是因为我们现在实现的双链表具有头节点(哨兵卫),正是因为有了它,我们就不用传二级指针了。为什么了,前面我们讲过,单链表之所以传入的是二级指针。那是因为在特殊情况下我们的第一个节点会变成空指针。有哨兵卫后就不同了,假如我们现在双链表中只存在一个节点,我们将它删除,只需要将phead的prev与next均指向自身就可以了,而如果没有哨兵卫,我们就要将phead置成空指针,这样传入的参数就改变了,那么就需要传二级指针。

        其实就一点,如果链表有哨兵卫的话就直接传入一级指针就可以了。

尾删节点

  1. //尾删节点
  2. void LPopBack(LNode* phead)
  3. {
  4. assert(phead);
  5. //防止传入的链表是一个空链表
  6. assert(phead->prev != phead);
  7. LNode* tail = phead->prev;
  8. LNode* tailPrev = tail->prev;
  9. //每个节点都是动态开辟出来的,记得释放
  10. free(tail);
  11. phead->prev = tailPrev;
  12. tailPrev->next = phead;
  13. }

 头插节点

  1. //头插节点
  2. void LPushFront(LNode* phead, LDataType x)
  3. {
  4. assert(phead);
  5. LNode* newnode = BuyListNode(x);
  6. LNode* first = phead->next;
  7. phead->next = newnode;
  8. first->prev = newnode;
  9. newnode->prev = phead;
  10. newnode->next = first;
  11. }

 头删节点

  1. //头删节点
  2. void LPopFront(LNode* phead)
  3. {
  4. assert(phead);
  5. //防止链表为空
  6. assert(phead->next != phead);
  7. LNode* first = phead->next;
  8. LNode* newfirst = first->next;
  9. free(first);
  10. phead->next = newfirst;
  11. newfirst->prev = phead;
  12. }

 指定位置前插入

  1. //指定位置前插入
  2. void LInsert(LNode* pos, LDataType x)
  3. {
  4. assert(pos);
  5. LNode* newnode = BuyListNode(x);
  6. LNode* posPrev = pos->prev;
  7. newnode->next = pos;
  8. pos->prev = newnode;
  9. posPrev->next = newnode;
  10. newnode->prev = posPrev;
  11. }

 删除指定位置节点

  1. //删除指定位置节点
  2. void LErase(LNode* pos)
  3. {
  4. assert(pos);
  5. LNode* posNext = pos->next;
  6. LNode* posPrev = pos->prev;
  7. free(pos);
  8. posPrev->next = posNext;
  9. posNext->prev = posPrev;
  10. }

改写插入删除 

有了删除指定位置节点函数和指定位置前插入函数,我们就可以改写尾插尾删、头插头删函数。

  1. //尾插节点
  2. void LPushFront(LNode* phead, LDataType x)
  3. {
  4. LInsert(phead, x);
  5. }
  6. //尾删节点
  7. void LPopBack(LNode* phead)
  8. {
  9. LErase(phead->prev);
  10. }
  11. //头插节点
  12. void LPushFront(LNode* phead, LDataType x)
  13. {
  14. LInsert(phead->next, x);
  15. }
  16. //头删节点
  17. void LPopFront(LNode* phead)
  18. {
  19. LErase(phead->next);
  20. }

判断链表是否为空

  1. //判断链表是否为空
  2. bool LIsEmpty(LNode* phead)
  3. {
  4. assert(phead);
  5. return phead == phead->next;
  6. }

计算链表长度

  1. //计算链表长度
  2. int LSize(LNode* phead)
  3. {
  4. int count = 0;
  5. LNode* cur = phead->next;
  6. while (cur != phead)
  7. {
  8. count++;
  9. cur = cur->next;
  10. }
  11. return count;
  12. }

销毁链表

  1. //销毁链表
  2. void LDestroy(LNode* phead)
  3. {
  4. assert(phead);
  5. LNode* cur = phead->next;
  6. //将每个节点都释放
  7. while (cur != phead)
  8. {
  9. LNode* next = cur->next;
  10. free(cur);
  11. cur = next;
  12. }
  13. //自己手动将phead置为空指针
  14. free(phead);
  15. }

双链表完整代码

List.h

  1. #pragma once
  2. #define _CRT_SECURE_NO_WARNINGS 1
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <stdbool.h>
  6. #include <assert.h>
  7. #include <stdbool.h>
  8. typedef int LDataType;
  9. typedef struct ListNode
  10. {
  11. struct ListNode* prev;
  12. struct ListNode* next;
  13. LDataType data;
  14. }LNode;
  15. //初始化双链表
  16. LNode* LInit();
  17. //打印双链表
  18. void LPrint(LNode* phead);
  19. //创建一个节点
  20. LNode* BuyListNode(LDataType x);
  21. //尾插尾删
  22. void LPushBack(LNode* phead, LDataType x);
  23. void LPopBack(LNode* phead);
  24. //头插头删
  25. void LPushFront(LNode* phead, LDataType x);
  26. void LPopFront(LNode* phead);
  27. //在指定位置前插入,删除指定位置
  28. void LErase(LNode* pos);
  29. void LInsert(LNode* pos, LDataType x);
  30. //销毁链表
  31. void LDestroy(LNode* phead);
  32. //判断链表是否为空
  33. bool LIsEmpty(LNode* phead);
  34. //计算链表节点个数
  35. size_t LSize(LNode* phead);

List.c

  1. #include "List.h"
  2. LNode* LInit()
  3. {
  4. LNode* newnode = (LNode*)malloc(sizeof(LNode));
  5. if (newnode == NULL)
  6. {
  7. perror("malloc fail");
  8. exit(-1);
  9. }
  10. newnode->next = newnode;
  11. newnode->prev = newnode;
  12. return newnode;
  13. }
  14. LNode* BuyListNode(LDataType x)
  15. {
  16. LNode* newnode = (LNode*)malloc(sizeof(LNode));
  17. if (newnode == NULL)
  18. {
  19. perror("malloc fail");
  20. exit(-1);
  21. }
  22. newnode->data = x;
  23. newnode->next = NULL;
  24. newnode->prev = NULL;
  25. return newnode;
  26. }
  27. void LPushBack(LNode* phead,LDataType x)
  28. {
  29. assert(phead);
  30. LNode* newnode = BuyListNode(x);
  31. LNode* tail = phead->prev;
  32. tail->next = newnode;
  33. phead->prev = newnode;
  34. newnode->next = phead;
  35. newnode->prev = tail;
  36. //LInsert(phead, x);
  37. }
  38. void LPopBack(LNode* phead)
  39. {
  40. assert(phead);
  41. assert(phead->prev != phead);
  42. LNode* tail = phead->prev;
  43. LNode* tailPrev = tail->prev;
  44. free(tail);
  45. phead->prev = tailPrev;
  46. tailPrev->next = phead;
  47. //LErase(phead->prev);
  48. }
  49. void LPrint(LNode* phead)
  50. {
  51. assert(phead);
  52. LNode* cur = phead->next;
  53. while (cur != phead)
  54. {
  55. printf("%d ", cur->data);
  56. cur = cur->next;
  57. }
  58. printf("\n");
  59. }
  60. void LPushFront(LNode* phead, LDataType x)
  61. {
  62. assert(phead);
  63. LNode* newnode = BuyListNode(x);
  64. LNode* first = phead->next;
  65. phead->next = newnode;
  66. first->prev = newnode;
  67. newnode->prev = phead;
  68. newnode->next = first;
  69. //LInsert(phead->next, x);
  70. }
  71. void LPopFront(LNode* phead)
  72. {
  73. //assert(phead);
  74. //assert(phead->next != phead);
  75. //LNode* first = phead->next;
  76. //LNode* newfirst = first->next;
  77. //free(first);
  78. //phead->next = newfirst;
  79. //newfirst->prev = phead;
  80. LErase(phead->next);
  81. }
  82. LNode* LFind(LNode* phead, LDataType x)
  83. {
  84. assert(phead);
  85. LNode* 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. void LErase(LNode* pos)
  97. {
  98. assert(pos);
  99. LNode* posNext = pos->next;
  100. LNode* posPrev = pos->prev;
  101. free(pos);
  102. posPrev->next = posNext;
  103. posNext->prev = posPrev;
  104. }
  105. void LInsert(LNode* pos, LDataType x)
  106. {
  107. assert(pos);
  108. LNode* newnode = BuyListNode(x);
  109. LNode* posPrev = pos->prev;
  110. newnode->next = pos;
  111. pos->prev = newnode;
  112. posPrev->next = newnode;
  113. newnode->prev = posPrev;
  114. }
  115. void LDestroy(LNode* phead)
  116. {
  117. assert(phead);
  118. LNode* cur = phead->next;
  119. while (cur != phead)
  120. {
  121. LNode* next = cur->next;
  122. free(cur);
  123. cur = next;
  124. }
  125. //自己手动置为空指针
  126. free(phead);
  127. }
  128. bool LIsEmpty(LNode* phead)
  129. {
  130. assert(phead);
  131. return phead == phead->next;
  132. }
  133. int LSize(LNode* phead)
  134. {
  135. int count = 0;
  136. LNode* cur = phead->next;
  137. while (cur != phead)
  138. {
  139. count++;
  140. cur = cur->next;
  141. }
  142. return count;
  143. }

浅谈链表及顺序表

链表和顺序表都作为线性表,他们之间有何异同呢?如以下表格:

不同点顺序表链表
存储空间上物理上一定连续逻辑上连续,物理空间上不一定连续
随机访问支持 O(1)不支持 O(N)
任意位置插入和删除元素可能需要搬移元素,效率低;O(N)只需修改指针指向
插入动态顺序表,空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁
缓存利用率

以上就是双链表实现的全部内容了,希望能够帮助到大家。如果有不对的地方请各位大佬在评论区指正(鞠躬)。

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/856651?site
推荐阅读
相关标签
  

闽ICP备14008679号