当前位置:   article > 正文

C语言数据结构——带头双向循环链表_listnode** current = head;的意思

listnode** current = head;的意思

 

目录

1、链表的分类

2、带头双向循环链表

2.1 概念及其结构分析 

2.2 带头双向循环链表的实现 

2.3 带头双向循环链表源码


1、链表的分类

实际中链表的结构非常多样,组合起来就有 8 种链表结构:无头单向非循环链表、无头单向循环链表、带头单向非循环链表、带头单向循环链表、无头双向非循环链表、无头双向循环链表、带头双向非循环链表、带头双向循环链表​​​​​​等。上篇博客讲到无头单向非循环链表,该链表结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。篇博客讲到的是带头双向循环链表 。带头双向循环链表结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。

2、带头双向循环链表

2.1 概念及其结构分析 

双链表,也是基于单链表的。单链表是单向的,有一个头结点,一个尾结点,要访问任何结点,都必须知道头结点,不能逆着进行。而双链表则是添加了一个指针域,通过两个指针域,分别指向结点的前结点和后结点。这样的话,可以通过双链表的任何结点,访问到它的前结点和后结点。但双链表还是不够灵活,所以,实际编程中比较常用的是带头双向循环链表,但带头双向循环链表使用较为麻烦。 

链表的一个存储结点包含三个部分内容: 

 data 部分称为数据域,用于存储线性表的一个数据元素;previous 和 next 两部分称为指针域,都是用于存放一个指针。previous 指针是指向该链表上一个结点的地址,next 指针是指向该链表下一个结点的地址。

带头是什么意思?意思就是带哨兵卫的链表,也就是说哨兵卫就是链表的头,但是真正的头结点不是哨兵卫结点,而是哨兵卫结点的下一个结点。带头的链表不需要我们单独定义一个指针用来存储头结点的地址,当然传参的时候也不需要使用二级指针了。还有,哨兵卫是不存任何有效数据的,链表的长度也不行。因为如果链表要存储的数据类型为 char 类型,当链表的长度超过 128 后,那么就会发生错误。至于链表循环是什么样子,请看逻辑图的结构。 

下面对带头双向循环链表的逻辑结构和物理图结构作分析。

双链表逻辑结构:  

逻辑结构:想象出来的,形象,方便理解。结构图如下: 

双链表物理结构:

物理结构:在内存中实实在在如何存储的。物理结构图如下:

2.2 带头双向循环链表的实现 

为了有效地存储结点数据,并且实现双链表的链式功能,可建立 ListNode 结构体。代码如下:

  1. typedef int LTDataType;
  2. typedef struct ListNode
  3. {
  4. LTDataType data; //结点数据
  5. struct ListNode* next; //指向下一个结点的地址
  6. struct ListNode* previous;//指向前一个结点的地址
  7. }ListNode;

1.链表的初始化(哨兵卫结点的创建)

  1. ListNode* ListInint()
  2. {
  3. ListNode* head = (ListNode*)malloc(sizeof(ListNode));
  4. head->next = head;
  5. head->previous = head;
  6. return head;
  7. }

 带头双向循环链表在插入数据之前,要先创建好头链表,再进行其他相关的操作。因为是循环双链表,所以需要把 previous 和 next 指向本身,最后返回 head 的地址。这个初始化的作用就是为了创建一个充当头结点的哨兵卫结点,这个头结点不存储任何有效数据。如下图:

2.创建一个结点

  1. ListNode* BuyListNode(LTDataType x)//创建一个链表结点
  2. {
  3. ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  4. newnode->next = NULL;
  5. newnode->previous = NULL;
  6. newnode->data = x;
  7. return newnode;
  8. }

使用 malloc() 或者使用动态内存开辟的其他函数创建链表结点,因为是双向链表结点,所以有两个指针域。

3.尾插

  1. void ListPushBack(ListNode* head, LTDataType x)//尾插
  2. {
  3. //尾插之前找到尾结点
  4. assert(head);//检查哨兵卫结点是否为空
  5. ListNode* newnode = BuyListNode(x);//接收创建的新结点的地址
  6. ListNode* tail = head->previous;//找到尾结点
  7. //开始尾插
  8. tail->next = newnode;//尾结点的下一个就是newnode
  9. newnode->previous = tail;//newnode的前一个就是尾结点
  10. newnode->next = head;//newnode的下一结点是head(哨兵卫结点)
  11. head->previous = newnode;//哨兵位结点的前一个是newnode
  12. }

 4.尾删

  1. void ListPopBack(ListNode* head)//尾删
  2. {
  3. //尾删之前先找到尾结点
  4. assert(head);//检查哨兵卫结点是否为空
  5. assert(head->next!=head);//删除到哨兵卫结点就停止删除
  6. //尾删之前先找到尾结点和尾结点之前的一个结点
  7. ListNode* tail = head->previous;//尾结点
  8. ListNode* tailPrevious = tail->previous;//尾结点的前一个结点
  9. //删除之前,先把尾结点的前一个结点和哨兵卫结点链接好
  10. tailPrevious->next = head;
  11. head->previous = tailPrevious;
  12. free(tail);//开始删除
  13. tail = NULL;
  14. }

5.头插

  1. void ListPushFront(ListNode* head, LTDataType x)//头插
  2. {
  3. //头插之前先找到头
  4. assert(head);//检查哨兵卫结点是否为空
  5. ListNode* newnode = BuyListNode(x);//接收创建的新结点的地址
  6. ListNode* current = head->next;//找到头
  7. //开始头插
  8. head->next = newnode;
  9. newnode->previous = head;//先把要插入的新结点和哨兵卫结点链接好
  10. //再把插入的新结点和current(正真的头)链接好
  11. newnode->next = current;
  12. current->previous = newnode;
  13. }

6.头删

  1. void ListPopFront(ListNode* head)//头删
  2. {
  3. //头删之前先找到头
  4. assert(head);//检查哨兵卫结点是否为空
  5. assert(head->next != head);//删到哨兵卫结点就停止删除
  6. ListNode* current = head->next;//找到头
  7. ListNode* currentNext = current->next;//找到头的下一个
  8. //删除之前先把哨兵卫结点和头结点的下一个结点链接好
  9. head->next = currentNext;
  10. currentNext->previous = head;
  11. free(current);//开始删除
  12. current = NULL;
  13. }

7.查找数据 x 的地址

  1. ListNode* ListFind(ListNode* head, LTDataType x)//查找数据 x 的地址
  2. {
  3. assert(head);//检查哨兵卫结点是否为空
  4. ListNode* current = head->next;//从头开始遍历查找
  5. while (current != head)//直到循环到哨兵卫就停止
  6. {
  7. if (current->data == x)//找到数据 x ,就返回数据 x 的地址
  8. {
  9. return current;
  10. }
  11. current = current->next;//当前结点就等于下一个结点
  12. }
  13. return NULL;//找不到就返回空
  14. }

8.在 pos 位置之前插入一个数据

  1. void ListInsert(ListNode* pos, LTDataType x)//在 pos 位置之前插入一个数据
  2. {
  3. assert(pos);//判断pos是否为空,如果 pos 为空,那么在空的前面插入一个数据,就是不可能了
  4. ListNode* newnode = BuyListNode(x);//接收创建的新结点的地址
  5. //在插入之前先找到 pos 的前一个结点
  6. ListNode* posPrevious = pos->previous;//pos位置的前一个结点
  7. //开始插入,先让 pos 的前一个结点和新结点链接好
  8. posPrevious->next = newnode;
  9. newnode->previous = posPrevious;
  10. //再让新结点和 pos 结点链接好
  11. newnode->next = pos;
  12. pos->previous = newnode;
  13. }

假设 pos 位置如图下所示: 

 9.删除 pos 位置的结点

  1. void ListErase(ListNode* pos)//删除 pos 位置的结点
  2. {
  3. //删除掉 pos 位置的结点,就要找到 pos 位置的前一个结点和下一个结点
  4. assert(pos);//判断pos是否为空,如果 pos 为空,删除一个空,就没有意义了
  5. ListNode* posPrevious = pos->previous;//找到 pos 前一个结点
  6. ListNode* posNext = pos->next;//找到 pos 下一个结点
  7. //开始链接 pos 的前一个结点和 pos 的下一个结点
  8. posPrevious->next = posNext;
  9. posNext->previous = posPrevious;
  10. //开始删除
  11. free(pos);
  12. pos = NULL;
  13. }

 10.释放链表结点

  1. void ListDestroy(ListNode** head)//释放链表结点
  2. {
  3. assert(*head);//判断哨兵卫结点是否为空
  4. ListNode* current = (*head)->next;//从头结点开始释放
  5. while (current != *head)
  6. {
  7. ListNode* next = current->next;//记录释放结点的下一个结点
  8. free(current);//释放链表结点
  9. current = next;
  10. }
  11. free(*head);//释放哨兵卫结点
  12. *head = NULL;//将指向哨兵卫结点的指针置空,防止成为野指针
  13. }

从头结点开始释放,释放之前记录下一个结点,防止找不到下一个结点。直到循环到哨兵卫结点。这里为什么传二级指针呢?是因为释放了哨兵卫结点后,要把指向哨兵卫结点的指针置空,防止成为野指针,再次强调一下,需要改变指针的指向,传二级指针。也就是说,我们要改变 head 指针的指向,需要我们传 head 的地址,才能改变 head 的指向。

11.打印链表节点数据

  1. void ListPrint(ListNode* head)//打印链表节点数据
  2. {
  3. assert(head);//判断哨兵卫结点是不是空
  4. ListNode* current = head->next;//从头结点开始打印
  5. while (current!= head)
  6. {
  7. printf("%d ", current->data);
  8. current=current->next;
  9. }
  10. printf("\n");
  11. }

2.3 带头双向循环链表源码

 list.h

  1. #pragma once
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<assert.h>
  5. typedef int LTDataType;
  6. typedef struct ListNode
  7. {
  8. LTDataType data; //结点数据
  9. struct ListNode* next; //指向下一个结点的地址
  10. struct ListNode* previous;//指向前一个结点的地址
  11. }ListNode;
  12. //初始化
  13. ListNode* ListInint();
  14. //打印链表结点数据
  15. void ListPrint(ListNode* head);
  16. //创建一个链表结点
  17. ListNode* BuyListNode(LTDataType x);
  18. //尾插
  19. void ListPushBack(ListNode* head, LTDataType x);
  20. //尾删
  21. void ListPopBack(ListNode* head);
  22. //头插
  23. void ListPushFront(ListNode* head, LTDataType x);
  24. //头删
  25. void ListPopFront(ListNode* head);
  26. //查找数据 x 的地址
  27. ListNode* ListFind(ListNode* head, LTDataType x);
  28. //在 pos 之前插入一个数据
  29. void ListInsert(ListNode* pos, LTDataType x);
  30. //删除 pos 地址的结点
  31. void ListErase(ListNode* pos);
  32. //释放链表结点
  33. void ListDestroy(ListNode** head);

 list.c

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"list.h"
  3. ListNode* ListInint()
  4. {
  5. ListNode* head = (ListNode*)malloc(sizeof(ListNode));
  6. head->next = head;
  7. head->previous = head;
  8. return head;
  9. }
  10. void ListPrint(ListNode* head)//打印链表节点数据
  11. {
  12. assert(head);//判断哨兵卫结点是不是空
  13. ListNode* current = head->next;//从头结点开始打印
  14. while (current!= head)
  15. {
  16. printf("%d ", current->data);
  17. current=current->next;
  18. }
  19. printf("\n");
  20. }
  21. ListNode* BuyListNode(LTDataType x)//创建一个链表结点
  22. {
  23. ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  24. newnode->next = NULL;
  25. newnode->previous = NULL;
  26. newnode->data = x;
  27. return newnode;
  28. }
  29. void ListPushBack(ListNode* head, LTDataType x)//尾插
  30. {
  31. //尾插之前找到尾结点
  32. assert(head);//检查哨兵卫结点是否为空
  33. ListNode* newnode = BuyListNode(x);//接收创建的新结点的地址
  34. ListNode* tail = head->previous;//找到尾结点
  35. //开始尾插
  36. tail->next = newnode;//尾结点的下一个就是newnode
  37. newnode->previous = tail;//newnode的前一个就是尾结点
  38. newnode->next = head;//newnode的下一结点是head(哨兵卫结点)
  39. head->previous = newnode;//哨兵位结点的前一个是newnode
  40. }
  41. void ListPopBack(ListNode* head)//尾删
  42. {
  43. //尾删之前先找到尾结点
  44. assert(head);//检查哨兵卫结点是否为空
  45. assert(head->next!=head);//删除到哨兵卫结点就停止删除
  46. //尾删之前先找到尾结点和尾结点之前的一个结点
  47. ListNode* tail = head->previous;//尾结点
  48. ListNode* tailPrevious = tail->previous;//尾结点的前一个结点
  49. //删除之前,先把尾结点的前一个结点和哨兵卫结点链接好
  50. tailPrevious->next = head;
  51. head->previous = tailPrevious;
  52. free(tail);//开始删除
  53. tail = NULL;
  54. }
  55. void ListPushFront(ListNode* head, LTDataType x)//头插
  56. {
  57. //头插之前先找到头
  58. assert(head);//检查哨兵卫结点是否为空
  59. ListNode* newnode = BuyListNode(x);//接收创建的新结点的地址
  60. ListNode* current = head->next;//找到头
  61. //开始头插
  62. head->next = newnode;
  63. newnode->previous = head;//先把要插入的新结点和哨兵卫结点链接好
  64. //再把插入的新结点和current(正真的头)链接好
  65. newnode->next = current;
  66. current->previous = newnode;
  67. }
  68. void ListPopFront(ListNode* head)//头删
  69. {
  70. //头删之前先找到头
  71. assert(head);//检查哨兵卫结点是否为空
  72. assert(head->next != head);//删到哨兵卫结点就停止删除
  73. ListNode* current = head->next;//找到头
  74. ListNode* currentNext = current->next;//找到头的下一个
  75. //删除之前先把哨兵卫结点和头结点的下一个结点链接好
  76. head->next = currentNext;
  77. currentNext->previous = head;
  78. free(current);//开始删除
  79. current = NULL;
  80. }
  81. ListNode* ListFind(ListNode* head, LTDataType x)//查找数据 x 的地址
  82. {
  83. assert(head);//检查哨兵卫结点是否为空
  84. ListNode* current = head->next;//从头开始遍历查找
  85. while (current != head)//直到循环到哨兵卫就停止
  86. {
  87. if (current->data == x)//找到数据 x ,就返回数据 x 的地址
  88. {
  89. return current;
  90. }
  91. current = current->next;
  92. }
  93. return NULL;//找不到就返回空
  94. }
  95. void ListInsert(ListNode* pos, LTDataType x)//在 pos 之前插入一个数据
  96. {
  97. assert(pos);//判断pos是否为空,如果 pos 为空,那么在空的前面插入一个数据,就是不可能了
  98. ListNode* newnode = BuyListNode(x);//接收创建的新结点的地址
  99. //在插入之前先找到 pos 的前一个结点
  100. ListNode* posPrevious = pos->previous;//pos位置的前一个结点
  101. //开始插入,先让 pos 的前一个结点和新结点链接好
  102. posPrevious->next = newnode;
  103. newnode->previous = posPrevious;
  104. //再让新结点和 pos 结点链接好
  105. newnode->next = pos;
  106. pos->previous = newnode;
  107. }
  108. void ListErase(ListNode* pos)//删除 pos 位置的结点
  109. {
  110. //删除掉 pos 位置的结点,就要找到 pos 位置的前一个结点和下一个结点
  111. assert(pos);//判断pos是否为空,如果 pos 为空,删除一个空,就没有意义了
  112. ListNode* posPrevious = pos->previous;//找到 pos 前一个结点
  113. ListNode* posNext = pos->next;//找到 pos 下一个结点
  114. //开始链接 pos 的前一个结点和 pos 的下一个结点
  115. posPrevious->next = posNext;
  116. posNext->previous = posPrevious;
  117. //开始删除
  118. free(pos);
  119. pos = NULL;
  120. }
  121. void ListDestroy(ListNode** head)//释放链表结点
  122. {
  123. assert(*head);//判断哨兵卫结点是否为空
  124. ListNode* current = (*head)->next;//从头结点开始释放
  125. while (current != *head)
  126. {
  127. ListNode* next = current->next;//记录释放结点的下一个结点
  128. free(current);//释放链表结点
  129. current = next;
  130. }
  131. free(*head);//释放哨兵卫结点
  132. *head = NULL;//将指向哨兵卫结点的指针置空,防止成为野指针
  133. }

 test.c

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"list.h"
  3. //带头双向循环链表
  4. void TestList()
  5. {
  6. ListNode* head = ListInint();//创建哨兵位头结点
  7. ListPushBack(head, 1);//尾插
  8. ListPushBack(head, 2);
  9. ListPushBack(head, 3);
  10. ListPushBack(head, 4);
  11. ListPushBack(head, 5);
  12. printf("尾插1、2、3、4、5>\n");
  13. ListPrint(head);//打印
  14. ListPopBack(head);//尾删
  15. ListPopBack(head);
  16. ListPopBack(head);
  17. ListPopBack(head);
  18. printf("尾删三个结点>\n");
  19. ListPrint(head);
  20. ListPushFront(head, 1);//头插
  21. ListPushFront(head, 2);
  22. ListPushFront(head, 3);
  23. ListPushFront(head, 4);
  24. ListPushFront(head, 5);
  25. printf("头插1、2、3、4、5>\n");
  26. ListPrint(head);
  27. ListPopFront(head);//头删
  28. ListPopFront(head);
  29. printf("头删两个结点>\n");
  30. ListPrint(head);
  31. ListNode* pos = ListFind(head, 2);
  32. ListInsert(pos, 20);
  33. printf("在 pos(数据 2 ) 位置之前插入( 20 )一个结点>\n");
  34. ListPrint(head);
  35. ListErase(pos);//删除pos位置的结点
  36. printf("删除 pos(数据 2 ) 位置的结点>\n");
  37. ListPrint(head);
  38. ListDestroy(&head);//释放链表结点。因为释放其他结点的同时,也要把哨兵卫结点给释放了,
  39. //释放哨兵卫的同时需要把 head 指针的指向置空,所以需要传 head 的地址。需要二级指针来接收指针 head 的地址
  40. }
  41. int main()
  42. {
  43. TestList();
  44. return 0;
  45. }

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

闽ICP备14008679号