• 双向带头循环链表之重拳出击


    目录

    一、 链表的8种结构

    二、 双向带头循环链表的实现

    结构的创建和初始化 

     申请结点

    初始化

    尾插

    打印

    头插

    尾删 

    头删

    判空

     链表长度

     在pos位置之前插入结点

    删除pos位置的结点

    三、完整代码

     总结


    一、 链表的8种结构

    1、不带头单向循环结构
    2、不带头单向⾮循环结构
    3、不带头双向循环结构
    4、不带头双向⾮循环结构
    5、带头单向循环结构
    6、带头单向⾮循环结构
    7、带头双向循环结构
    8、带头双向⾮循环结构

    但是其中我们最常用的就两种,一种是我之前说过的单链表 ,另一种就是我今天要说的双向带头循环链表。双向带头循环链表是非常实用的链表,它结构复杂但是操作简单,等你学完你也就瞧不上其他链表了。

     双向带头循环链表的结构是这样的

    好让我们上手写一下。

    二、 双向带头循环链表的实现

    结构的创建和初始化 

     首先我们写一下我们所需要的头文件

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <assert.h>
    4. #include <stdbool.h>

    其次我们在创建它的结构时要有一个前驱指针,还要有一个后继指针 

    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* ListInit();
    3. //尾插
    4. void ListPushBack(LTNode* phead, LTDataType x);
    5. //打印
    6. void ListPrint(LTNode* phead);
    7. //头插
    8. void ListPushFront(LTNode* phead, LTDataType x);
    9. //尾删
    10. void ListPopBack(LTNode* phead);
    11. //头删
    12. void ListPopFront(LTNode* phead);
    13. //判空
    14. bool ListEmpty(LTNode* phead);
    15. //在pos位置之前插入x
    16. void ListInsert(LTNode* pos, LTDataType x);
    17. //删除pos位置的节点
    18. void ListErase(LTNode* pos);
    19. //链表长度
    20. int ListSize(LTNode* phead);
    21. //销毁链表
    22. void ListDestory(LTNode* phead);

     申请结点

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

    初始化

    初始化的话有两种方法可以用

    方法一:二级指针

    1. void ListInit(LTNode** pphead)
    2. {
    3. *pphead = BuyListNode(-1);
    4. (*pphead)->next = *pphead;
    5. (*pphead)->prev = *pphead;
    6. }

     方法二:用结构体指针作为返回值 申请结点

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

    尾插

     如果学习了单链表的话,这个对于你来说不就是小菜一碟嘛

     当然跟单链表不同的是双向链表就不用像单链表那样遍历找尾了,因为它头结点的prev就指向了尾结点所以变得更简单了。

    链接过程

     代码实现

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

    当链表为空时,这个代码(也包括我下面的代码)也同样可以运行,不信你就顺着这个代码的思路理一遍

    打印

    代码实现: 

    1. void ListPrint(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. }

     注意:可不是从头开始打印,而是头结点的下一个位置,别忘了我们可是设了哨兵位的。 

    头插

    头插其实也很简单 

     代码实现: 

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

    当然你也可以不用像我这样定义一个next,但是你逻辑(链接的先后顺序)要捋顺,否则可是会出错的。

    尾删 

    代码实现

    1. void ListPopBack(LTNode* phead)
    2. {
    3.     assert(phead);
    4.     assert(! ListEmpty(phead));
    5.     assert(phead->next!=phead);
    6.     LTNode* tail = phead->prev;
    7.     LTNode* tailprev = tail->prev;
    8.     free(tail);
    9.     phead->prev = tailprev;
    10.     tailprev->next = phead;
    11. }

    双向链表的尾删其实也不难,我们可以先用phead->prev找到尾结点,free点尾结点,之后再将新的尾结点和phead进行链接。

    头删

    代码实现

    1. void ListPopFront(LTNode* phead)
    2. {
    3. assert(phead);
    4. assert(!ListEmpty(phead));
    5. LTNode* head = phead->next;
    6. LTNode* headnext = head->next;
    7. free(head);
    8. phead->next = headnext;
    9. headnext->prev = phead;
    10. }

    思路和尾删的一样,phead->next找到头,free掉头结点,再将新的头和phead进行链接就完事了。

    判空

     上面的头删和尾删都离开不了判空。所以说判空其实挺重要的。

    代码实现

    1. bool ListEmpty(LTNode* phead)
    2. {
    3. assert(phead);
    4. return phead->next == phead;
    5. }

    两行代码就搞定了,是不是感觉有点不太相信。其实它真就值这两行代码。

     链表长度

    代码实现

    1. int ListSize(LTNode* phead)
    2. {
    3. assert(phead);
    4. LTNode* cur = phead->next;
    5. int size = 0;
    6. while (cur != phead)
    7. {
    8. size++;
    9. cur = cur->next;
    10. }
    11. return size;
    12. }

    得到链表的长度和打印链表的思路一致,我们要从头结点的下一个结点进行遍历,一直到cur==phead就结束,最后返回长度(size)就OK了。 

     

    销毁链表

    代码实现

    1. void ListDestory(LTNode* phead)
    2. {
    3. assert(phead);
    4. LTNode* cur = phead->next;
    5. int size = 0;
    6. while (cur != phead)
    7. {
    8. LTNode* next = cur->next;
    9. ListPopFront(cur);
    10. cur = next;
    11. }
    12. free(phead);
    13. }

    销毁的话和单链表是类似的,但是这里还要多一步,因为我们是带了哨兵位的头结点,我们要把这个哨兵位也删掉。

    最精彩的部分当然要最后讲啦

    曾经有个面试官面试的时候,叫人十分钟写出一个双链表,可别觉得面试官为难你,看了我下面讲的,你熟悉的话十分钟都不要就可以搞定。

     在pos位置之前插入结点

    代码实现

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

     我们要先判断pos位置为不为空,然后找到pos位置的前一个,申请新节点,最后再将(pos、prev、newnode)进行链接就行了

    删除pos位置的结点

    代码实现

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

    同样的我们先判断pos位置为不为空,然后我们在链接posprev和posnext,别忘了最后还要free掉pos结点。

    前面的头插、尾插、头删、尾删直接调用这两个函数就行了。 

    所以说十分钟写好一个双链表真的不是夸张。 


    三、完整代码

     头文件:

    1. #pragma once
    2. #include<stdio.h>
    3. #include<stdlib.h>
    4. #include<assert.h>
    5. #include<stdbool.h>
    6. typedef int LTDataType;
    7. typedef struct ListNode
    8. {
    9. struct ListNode* next;
    10. struct ListNode* prev;
    11. LTDataType data;
    12. }LTNode;
    13. //初始化
    14. //void ListInit(LTNode** pphead);
    15. LTNode* ListInit();
    16. //尾插
    17. void ListPushBack(LTNode* phead, LTDataType x);
    18. //打印
    19. void ListPrint(LTNode* phead);
    20. //头插
    21. void ListPushFront(LTNode* phead, LTDataType x);
    22. //尾删
    23. void ListPopBack(LTNode* phead);
    24. //头删
    25. void ListPopFront(LTNode* phead);
    26. //判空
    27. bool ListEmpty(LTNode* phead);
    28. //在pos位置之前插入x
    29. void ListInsert(LTNode* pos, LTDataType x);
    30. //删除pos位置的节点
    31. void ListErase(LTNode* pos);
    32. //链表长度
    33. int ListSize(LTNode* phead);
    34. //销毁链表
    35. void ListDestory(LTNode* phead);

    源文件

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include "Dlist.h"
    3. LTNode* BuyListNode(LTDataType x)
    4. {
    5. LTNode* node = (LTNode*)malloc(sizeof(LTNode));
    6. if (node == NULL)
    7. {
    8. perror("malloc fail");
    9. exit(-1);
    10. }
    11. node->data = x;
    12. node->next = NULL;
    13. node->prev = NULL;
    14. }
    15. //void ListInit(LTNode** pphead)
    16. //{
    17. // *pphead = BuyListNode(-1);
    18. // (*pphead)->next = *pphead;
    19. // (*pphead)->prev = *pphead;
    20. //}
    21. LTNode* ListInit()
    22. {
    23. LTNode* phead= BuyListNode(-1);
    24. phead->next = phead;
    25. phead->prev = phead;
    26. return phead;
    27. }
    28. void ListPushBack(LTNode* phead, LTDataType x)
    29. {
    30. assert(phead);
    31. /*LTNode* newnode = BuyListNode(x);
    32. LTNode* tail = phead->prev;
    33. newnode->prev = tail;
    34. tail->next = newnode;
    35. newnode->next = phead;
    36. phead->prev = newnode;*/
    37. ListInsert(phead, x);
    38. }
    39. void ListPrint(LTNode* phead)
    40. {
    41. assert(phead);
    42. LTNode* cur = phead->next;
    43. while (cur != phead)
    44. {
    45. printf("%d ", cur->data);
    46. cur = cur->next;
    47. }
    48. printf("\n");
    49. }
    50. void ListPushFront(LTNode* phead, LTDataType x)
    51. {
    52. assert(phead);
    53. //LTNode* newnode= BuyListNode(x);
    54. 1
    55. //LTNode* next = phead->next;
    56. //phead->next = newnode;
    57. //newnode->prev = phead;
    58. //newnode->next = next;
    59. //next->prev = newnode;
    60. 2
    61. //phead->next->prev = newnode;
    62. //newnode->next = phead->next;
    63. //phead->next = newnode;
    64. //newnode->prev = phead;
    65. ListInsert(phead->next, x);
    66. }
    67. void ListPopBack(LTNode* phead)
    68. {
    69. assert(phead);
    70. assert(! ListEmpty(phead));
    71. /*assert(phead->next!=phead);
    72. LTNode* tail = phead->prev;
    73. LTNode* tailprev = tail->prev;
    74. free(tail);
    75. phead->prev = tailprev;
    76. tailprev->next = phead;*/
    77. ListErase(phead->prev);
    78. }
    79. void ListPopFront(LTNode* phead)
    80. {
    81. assert(phead);
    82. assert(!ListEmpty(phead));
    83. /*LTNode* head = phead->next;
    84. LTNode* headnext = head->next;
    85. free(head);
    86. phead->next = headnext;
    87. headnext->prev = phead;*/
    88. ListErase(phead->next);
    89. }
    90. bool ListEmpty(LTNode* phead)
    91. {
    92. assert(phead);
    93. return phead->next == phead;
    94. }
    95. void ListInsert(LTNode* pos, LTDataType x)
    96. {
    97. assert(pos);
    98. LTNode* prev = pos->prev;
    99. LTNode* newnode = BuyListNode(x);
    100. //prev newnode pos
    101. prev->next = newnode;
    102. newnode->prev = prev;
    103. newnode->next = pos;
    104. pos->prev = newnode;
    105. }
    106. void ListErase(LTNode* pos)
    107. {
    108. assert(pos);
    109. LTNode* posprev = pos->prev;
    110. LTNode* posnext = pos->next;
    111. posprev->next = posnext;
    112. posnext->prev = posprev;
    113. free(pos);
    114. }
    115. int ListSize(LTNode* phead)
    116. {
    117. assert(phead);
    118. LTNode* cur = phead->next;
    119. int size = 0;
    120. while (cur != phead)
    121. {
    122. size++;
    123. cur = cur->next;
    124. }
    125. return size;
    126. }
    127. void ListDestory(LTNode* phead)
    128. {
    129. assert(phead);
    130. LTNode* cur = phead->next;
    131. int size = 0;
    132. while (cur != phead)
    133. {
    134. LTNode* next = cur->next;
    135. ListErase(cur);
    136. cur = next;
    137. }
    138. free(phead);
    139. }

     总结:

    双链表的功能十分强大,虽然说结构可能复杂了一点但是插入删除的效率是非常的高,十分的好用。

    觉得内容有用的话,就给博主三连吧

     

     

  • 相关阅读:
    vue常见的指令
    AtCoder Beginner Contest 275(C,E 补)
    30天Python入门(第二十九天:深入了解Python API的使用2.0)
    Java 注解及其底层原理
    Go语言结构体
    微博头条文章开放接口报错 auth by Null spi
    2023/10/22总结
    [管理与领导-112]:IT人看清职场中的隐性规则 - 9 - 付出与回报的关系:先付出,后回报,不行就止损,这才是职场价值交换的本质
    js之原生ajax、Jquery-$.ajax、自定义ajax(post请求、get请求)
    DocTemplateTool - 可根据模板生成word或pdf文件的工具
  • 原文地址:https://blog.csdn.net/m0_62537611/article/details/125028346