• 【数据结构】带头节点双向循环链表


    目录

    顺序表和链表的区别

    带头双向循环链表分析

    带头双向循环链表结构:

     创建一个节点

    哨兵位头节点

    打印链表中的值

    在pos前插入

    删除pos位置的节点

    尾插

    尾删

    头插:

    头删

    链表元素查找

    总代码

     List.h文件

    List.c文件

    test.c文件


    顺序表和链表的区别

     

            顺序表:

    优点:尾插尾删效率高,下标的随机访问快。

    缺点:空间不够需要扩容(扩容空间大);头部或中间插入删除效率低,需要挪动数据。

            链表:

    优点:需要扩容,按需申请释放大小快节点内存;任意位置插入效率高——O(1).

    缺点:不支持下标随机访问。

    链表类型有8种:

     

     虽然有这么多的链表结构,但是实际中最常用的还是如下图两种结构:

    1、无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多的是作为其他数据结构的子结构,如哈希表、图的邻接表等。这两种结构在笔试面试种出现很多。

    2、带头双向循环链表结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然复杂,但是使用代码实现后以后会发现结构带来了很多优势,实现反而简单了。

    带头双向循环链表分析

    带头双向循环链表结构:

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

     

     创建一个节点

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

    哨兵位头节点

    1. LTNode* LTInit() //头节点(哨兵位)
    2. {
    3. LTNode* phead = BuyListNode(-1);
    4. phead->next = phead;
    5. phead->prev= phead;
    6. return phead;
    7. }

    打印链表中的值

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

    这里需要注意一下打印结束的标志,单链表的打印结束标志位最后一个节点为空,即while(cur);而带头双向循环链表的打印结束标志是最后一个节点指向哨兵位节点,即while(cur!= phead)。

    在pos前插入

    在pos位置之前插入值时,注意先将pos的前一个节点的地址记下来,再插入值:

     

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

    删除pos位置的节点

     删除pos位置的节点之前可以将pos的前一个节点和pos后一个节点的地址记下来:

     

     

    1. //删除pos位置
    2. void LTErase(LTNode* pos)
    3. {
    4. assert(pos);
    5. LTNode* prev = pos->prev;
    6. LTNode* next = pos->next;
    7. prev->next = next;
    8. next->prev = prev;
    9. free(pos);
    10. }

    尾插

    相当于在phead前面插入一个值,而哨兵位节点的地址没有改变。在带哨兵位双向循环链表中,哨兵位的前一个节点指向的地址(phead->prev)相当于最后一个,因为此链表中第一个节点的地址是phead->next:

    1. void LTPushBack(LTNode*phead,LTDataType x)
    2. {
    3. assert(phead);
    4. /*LTNode* newnode = BuyListNode(x);
    5. LTNode* tail = phead->prev;
    6. tail->next = newnode;
    7. newnode->prev = tail;
    8. newnode->next = phead;
    9. phead->prev = newnode;*/
    10. LTInsert(phead, x);//相当于在phead之前插入一个节点,此时的哨兵节点的位置仍然指向phead,phead->pre为尾节点(双链表)
    11. }

    尾删

    相当于将哨兵位前一个节点(phead->prev)给free掉:

    1. void LTPopBack(LTNode* phead)
    2. {
    3. assert(phead);
    4. assert(phead->next != NULL); // 哨兵位的下一位不为空
    5. /*
    6. LTNode* tail = phead->prev;
    7. LTNode* tailprev = tail->prev;
    8. phead->prev = tailprev;
    9. tailprev->next = phead;
    10. free(tail);
    11. */
    12. LTErase(phead->prev);
    13. }

    头插:

    相当于在哨兵位后一个节点之前插入:

     

    1. void LTPushFront(LTNode* phead, LTDataType x)
    2. {
    3. assert(phead);
    4. /*
    5. LTNode* newnode = BuyListNode(x);
    6. LTNode*first = phead->next; //first记住phead->next的地址;
    7. newnode->next = first;
    8. newnode->prev = phead;
    9. first->prev = newnode;
    10. phead->next = newnode;
    11. */
    12. LTInsert(phead->next, x);
    13. }

    头删

    删除哨兵位(phead)的后一个节点(phead->next):

     

    1. void LTPopFront(LTNode* phead)
    2. {
    3. assert(phead);
    4. assert(phead->next != NULL);//哨兵位的下一位即第一个节点不为空
    5. /*
    6. LTNode* first = phead->next;//first记住头节点地址
    7. LTNode* second = first->next;
    8. phead->next = second;
    9. second->prev = phead;
    10. free(first);
    11. */
    12. LTErase(phead->next);
    13. }

    注释掉的代码也是可以运行的,相当于LTErase(phead->next) 

    链表元素查找

    如果一直查找,直到找到哨兵位节点还没找到,则返回NULL;

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

    总代码

     List.h文件

    在此声明函数和定义链表的结构体

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. typedef int LTDataType;
    7. typedef struct ListNode
    8. {
    9. struct ListNode* next; //后一个节点地址
    10. struct ListNode* prev; //前一个节点地址
    11. LTDataType data;
    12. }LTNode;
    13. LTNode* BuyListNode(LTDataType x);//新建一个节点
    14. LTNode* LTInit();
    15. void LTPrint(LTNode* phead); //打印
    16. void LTPushBack(LTNode* phead, LTDataType x);//尾插
    17. void LTPopBack(LTNode* phead); //尾删
    18. void LTPushFront(LTNode* phead, LTDataType x);//头插
    19. void LTPopFront(LTNode* phead);
    20. LTNode* LTFind(LTNode* phead, LTDataType x);//查找双链表的元素
    21. //pos插入x
    22. void LTInsert(LTNode* pos, LTDataType x);
    23. //pos删除
    24. void LTErase(LTNode* pos);
    25. bool LTEmpty(LTNode* phead);
    26. size_t LTSize(LTNode* phead); //双链表长度
    27. void LTDestroy(LTNode* phead);

    List.c文件

    在此定义函数

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include"List.h"
    3. LTNode *BuyListNode(LTDataType x)
    4. {
    5. LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
    6. if (newnode == NULL)
    7. {
    8. perror("malloc fail");
    9. exit(-1);
    10. }
    11. newnode->prev = NULL;
    12. newnode->next = NULL;
    13. newnode->data = x;
    14. return newnode;
    15. }
    16. LTNode* LTInit() //头节点(哨兵位)
    17. {
    18. LTNode* phead = BuyListNode(-1);
    19. phead->next = phead;
    20. phead->prev= phead;
    21. return phead;
    22. }
    23. void LTPrint(LTNode*phead)
    24. {
    25. assert(phead);
    26. LTNode* cur = phead->next;
    27. while (cur != phead)
    28. {
    29. printf("%d ", cur->data);
    30. cur = cur->next;
    31. }
    32. printf("\n");
    33. }
    34. void LTPushBack(LTNode*phead,LTDataType x)
    35. {
    36. assert(phead);
    37. /*LTNode* newnode = BuyListNode(x);
    38. LTNode* tail = phead->prev;
    39. tail->next = newnode;
    40. newnode->prev = tail;
    41. newnode->next = phead;
    42. phead->prev = newnode;*/
    43. LTInsert(phead, x);//相当于在phead之前插入一个节点,此时的哨兵节点的位置仍然指向phead,phead->pre为尾节点(双链表)
    44. }
    45. void LTPopBack(LTNode* phead)
    46. {
    47. assert(phead);
    48. assert(phead->next != NULL); // 哨兵位的下一位不为空
    49. /*
    50. LTNode* tail = phead->prev;
    51. LTNode* tailprev = tail->prev;
    52. phead->prev = tailprev;
    53. tailprev->next = phead;
    54. free(tail);
    55. */
    56. LTErase(phead->prev);
    57. }
    58. void LTPushFront(LTNode* phead, LTDataType x)
    59. {
    60. assert(phead);
    61. /*
    62. LTNode* newnode = BuyListNode(x);
    63. LTNode*first = phead->next; //first记住phead->next的地址;
    64. newnode->next = first;
    65. newnode->prev = phead;
    66. first->prev = newnode;
    67. phead->next = newnode;
    68. */
    69. LTInsert(phead->next, x);
    70. }
    71. void LTPopFront(LTNode* phead)
    72. {
    73. assert(phead);
    74. assert(phead->next != NULL);//哨兵位的下一位即第一个节点不为空
    75. /*
    76. LTNode* first = phead->next;//first记住头节点地址
    77. LTNode* second = first->next;
    78. phead->next = second;
    79. second->prev = phead;
    80. free(first);
    81. */
    82. LTErase(phead->next);
    83. }
    84. //在pos之前插入x
    85. void LTInsert(LTNode* pos, LTDataType x)
    86. {
    87. assert(pos);
    88. LTNode* prev = pos->prev;
    89. LTNode* newnode = BuyListNode(x);
    90. prev->next = newnode;
    91. newnode->prev = prev;
    92. newnode->next = pos;
    93. pos->prev = newnode;
    94. }
    95. //删除pos位置
    96. void LTErase(LTNode* pos)
    97. {
    98. assert(pos);
    99. LTNode* prev = pos->prev;
    100. LTNode* next = pos->next;
    101. prev->next = next;
    102. next->prev = prev;
    103. free(pos);
    104. }
    105. bool LTEmpty(LTNode* phead)
    106. {
    107. assert(phead);
    108. /*
    109. if(phead->next==phead)
    110. {
    111. return true;
    112. }
    113. else
    114. {
    115. return false;
    116. }
    117. */
    118. return phead->next == phead;
    119. }
    120. size_t LTSize(LTNode* phead)
    121. {
    122. assert(phead);
    123. size_t size = 0;
    124. LTNode* cur = phead->next;
    125. while (cur != phead)
    126. {
    127. ++size;
    128. cur = cur->next;
    129. }
    130. return size;
    131. }
    132. void LTDestroy(LTNode* phead)
    133. {
    134. assert(phead);
    135. LTNode* cur = phead->next;
    136. LTNode* next = NULL;
    137. while (cur != phead)
    138. {
    139. next = cur->next;
    140. free(cur);
    141. cur = next;
    142. }
    143. free(phead);
    144. }
    145. LTNode* LTFind(LTNode* phead, LTDataType x)
    146. {
    147. assert(phead);
    148. LTNode* cur = phead->next;
    149. while (cur != phead)
    150. {
    151. if (cur->data == x)
    152. {
    153. return cur;
    154. }
    155. cur = cur->next;
    156. }
    157. return NULL;
    158. }

    test.c文件

    在此测试和放主函数

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include"List.h"
    3. void testLTNode1()
    4. {
    5. LTNode* phead = LTInit();
    6. LTPushBack(phead, 1);
    7. LTPushBack(phead, 2);
    8. LTPushBack(phead, 2);
    9. LTPushBack(phead, 3);
    10. LTPushBack(phead, 4);
    11. LTPrint(phead);
    12. LTPopBack(phead);
    13. LTPopBack(phead);
    14. LTPopBack(phead);
    15. LTPrint(phead);
    16. LTPushFront(phead, 1);
    17. LTPushFront(phead, 2);
    18. LTPushFront(phead, 3);
    19. LTPushFront(phead, 4);
    20. LTPrint(phead);
    21. LTPopFront(phead);
    22. LTPopFront(phead);
    23. LTPrint(phead);
    24. }
    25. void testLTNode2()
    26. {
    27. LTNode* phead = LTInit();
    28. LTPushFront(phead, 1);
    29. LTPushFront(phead, 2);
    30. LTPushFront(phead, 3);
    31. LTPushFront(phead, 4);
    32. LTPrint(phead);
    33. LTNode* pos = LTFind(phead, 3);
    34. if (pos)
    35. {
    36. pos->data *= 10;
    37. }
    38. LTPrint(phead);
    39. pos = LTFind(phead, 30);
    40. LTErase(pos);
    41. LTPrint(phead);
    42. pos = LTFind(phead, 4);
    43. LTInsert(pos, 3);
    44. LTPrint(phead);
    45. pos = NULL;
    46. LTDestroy(phead);
    47. phead = NULL;
    48. }
    49. int main()
    50. {
    51. //testLTNode1();
    52. testLTNode2();
    53. return 0;
    54. }

  • 相关阅读:
    通讯网关软件023——利用CommGate X2HTTP实现HTTP访问Modbus TCP
    上周热点回顾(10.10-10.16)
    js页面window.onload()=$(function(){}) 和$(docunment).ready(function(){})
    【2024秋招】2023-10-9 同花顺后端笔试题
    【数字图像处理】RGB 转灰度图
    【机器学习 吴恩达】 学习笔记一:引言、二:单变量线性回归、三:线性代数
    地址解析协议ARP
    淘宝商品详情API接口(H5端和APP端),淘宝详情页,商品属性接口,商品信息查询
    java基于springboot+vue的课程资源在线学习网站
    【c++每天一题】 字符串压缩
  • 原文地址:https://blog.csdn.net/weixin_53269843/article/details/127970125