经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C 语言 » 查看文章
C语言超详细讲解数据结构中双向带头循环链表
来源:jb51  时间:2022/4/11 14:27:32  对本文有异议

一、概念

前文我们已经学习了单向链表,并通过oj题目深入了解了带头节点的链表以及带环链表,来画张图总体回顾下:

在我们学习的链表中,其实总共有8种,都是单双向和带不带头以及带不带环的任意组合

今儿要学习的是双向 - 带头 - 循环链表,听名字就觉着结构很复杂,要比曾经学的单向 - 不带头 - 不循环 链表的结构复杂的多 ,确实也是。先来画张图整体感受下:

解释:

  • 双向:就要确保每个数据存两个指针next和prev。next指向下一个节点,prev指向上一个节点
  • 带头:带一个哨兵位的头节点在数据的最前头。
  • 循环:尾节点的next指向哨兵位头节点,而哨兵位的上一个节点prev指向尾节点,构成循环。

正文开始:

二、必备工作

2.1、创建双向链表结构

因为是双向链表,所以在结构体里头必然有两个指针,一个next指向下一个节点,一个prev指向上一个节点。

List.h 文件:

  1. //创建双向链表结构
  2. typedef int LTDataType; //方便后续更改数据类型,本文以int整型为主
  3. typedef struct ListNode
  4. {
  5. LTDataType data; //存储数据
  6. struct ListNode* next; //指向下一个
  7. struct ListNode* prev; //指向上一个
  8. }LTNode; //方便后续使用,不需要重复些struct

2.2、初始化链表

思路:

在初始化的时候要传地址,因为形参的改变不会影响实参,pphead的改变不会影响pList,要传pList的地址,用**pphead来接收,此时就要assert断言了,因为二级指针地址不可能位空。因为是双向循环链表,所以要将创建好的哨兵位节点的next和prev均指向自己。

List.h 文件:(1)

  1. //初始化链表(二级指针版)
  2. void ListInit(LTNode* pphead);

List.c 文件:(1)

  1. //初始化链表(二级指针版)
  2. void ListInit(LTNode** pphead)
  3. {
  4. //传二级指针,那么当然要断言
  5. assert(pphead);
  6. *pphead = BuyLTNode(0);//因为是带哨兵位的头节点,所以一开始就要给一个节点
  7. //为了循环,要让哨兵位的next和prev均指向自己
  8. (*pphead)->next = *pphead; //注意优先级,*pphead要加括号
  9. (*pphead)->prev = *pphead;
  10. }

注意:

上一种方法我们传的是二级指针,那么可以传一级指针吗,其实也是可以的,只需写个函数返回指针即可

List.h 文件:(2)

  1. //初始化链表(一级指针版本)
  2. LTNode* ListInit();

List.c 文件:(2)

  1. //初始化链表(一级指针版)
  2. LTNode* ListInit()
  3. {
  4. LTNode* phead = BuyLTNode(0);
  5. phead->next = phead;
  6. phead->prev = phead;
  7. return phead;
  8. }

2.3、动态申请节点

List.c 文件:

  1. //创建新节点
  2. LTNode* BuyLTNode(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. }

2.4、打印链表

思路:

既然是打印,首先要搞明白一点,哨兵位不用来存放有效数据,那么就不需要打印,定义一个cur指针来迭代,那么应该从phead的next开始打印,当cur走完一遍,重又回到phead的时候停止

List.h 文件:

  1. //打印链表
  2. void ListPrint(LTNode* phead);

List.c 文件:

  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. }

2.5、销毁链表

思路:

既然是销毁链表了,那么自然是要把链表的所有元素包括哨兵位都给销毁掉,但毕竟刚开始传phead的时候是不能为空的,所以要断言,在把所有有效数据销毁后最后再销毁哨兵位即可。

法一:遍历

定义一个指针cur,从phead的next第一个有效数据开始free,保存下一个,再free,依次遍历下去

法二:附用ListErase函数

此法也可以,不过每次Erase完,都会把前后两个节点再链接起来,虽说最后都会销毁,但duck不必多此一举,所有直接采用法一比较好

List.h 文件:

  1. //销毁链表
  2. void ListDestory(LTNode* phead);

List.c 文件:

  1. //销毁链表
  2. void ListDestory(LTNode* phead)
  3. {
  4. assert(phead);
  5. LTNode* cur = phead->next;
  6. //销毁从第一个节点到尾部的数据
  7. while (cur != phead)
  8. {
  9. LTNode* next = cur->next;
  10. //ListErase(cur);
  11. free(cur);
  12. cur = next;
  13. }
  14. //置空哨兵位节点phead
  15. free(phead);
  16. phead = NULL;
  17. }

Test.c 文件:

  1. void TestList7()
  2. {
  3. LTNode* pList = ListInit();
  4. for (int i = 1; i <= 7; i++)
  5. {
  6. ListPushBack(pList, i); //尾插7个数字
  7. }
  8. ListPrint(pList);//打印
  9. //销毁链表
  10. ListDestory(pList);
  11. pList = NULL;
  12. }

三、主要功能

3.1、在pos节点前插入数据

思路:

假设我们已经进行了尾插4个数字,现在想在数字3的前面插入30,那么首先就要查找有无数字3,若有,则插入。注意:这里需要用到后文才讲到的查找函数,这里直接引用了,详解看后文即可,问题不大!

首先,将30放到新创建的节点newnode里头,为了实现双向,要先把3的前一个数据2的next指向新节点newnode,把newnode的prev指向2,newnode的next指向3,3的prev指向newnode。

 List.h 文件:

  1. //在pos前插入数据
  2. void ListInsert(LTNode* pos, LTDataType x);

List.c 文件:

  1. //在pos前插入数据
  2. void ListInsert(LTNode* pos, LTDataType x)
  3. {
  4. assert(pos);
  5. //创建插入数据的新节点
  6. LTNode* newnode = BuyLTNode(x);
  7. //链接左侧
  8. pos->prev->next = newnode;
  9. newnode->prev = pos->prev;
  10. //链接右侧
  11. newnode->next = pos;
  12. pos->prev = newnode;
  13. }

Test.c 文件:

  1. void TestList3()
  2. {
  3. LTNode* pList = ListInit();
  4. for (int i = 1; i <= 7; i++)
  5. {
  6. ListPushBack(pList, i); //尾插7个数据
  7. }
  8. ListPrint(pList);//打印尾插的7个
  9. //寻找数字
  10. LTNode* pos = ListFind(pList, 3);
  11. if (pos)
  12. {
  13. ListInsert(pos, 30); //找到数字3就插入
  14. }
  15. ListPrint(pList);//打印
  16. }

效果如下:

尾插

思路:

首先,因为此链表是带哨兵位的头节点,所以头节点必然不为空,刚开始就要assert断言。其次,单链表尾插需要找尾,双向链表虽然也需要,不过非常简单,不需要再遍历链表了,因为哨兵位头节点的phead的上一个节点指向的就是尾,这就充分体现了双向循环的好处,找到了尾节点就需要再创建一个节点存储插入数据,方便尾插。

List.h 文件:

  1. //尾插
  2. void ListPushBack(LTNode* phead, LTDataType x);

List.c 文件:1.0

  1. //尾插1.0
  2. void ListPushBack(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead); //断言,防止头节点为空
  5. LTNode* tail = phead->prev; //找到尾节点,便于后续插入数据
  6. LTNode* newnode = BuyLTNode(x);//创建新节点
  7. //将此新插入的尾节点与上一个节点链接起来
  8. tail->next = newnode;
  9. newnode->prev = tail;
  10. //将尾节点与哨兵位phead链接起来构成循环
  11. newnode->next = phead;
  12. phead->prev = newnode;
  13. }

Test.c 文件:

  1. void TestList1()
  2. {
  3. //初始化(法一)
  4. /*LTNode* pList = NULL;
  5. ListInit(&pList);*/
  6. //初始化(法二)
  7. LTNode* pList = ListInit();
  8. for (int i = 1; i <= 7; i++)
  9. {
  10. ListPushBack(pList, i); //尾插7个数据
  11. }
  12. ListPrint(pList);//打印尾插的7个
  13. }

效果如下:

注意:

在上文中,我们学习了在pos前插入数据,那么设想一下,当pos就等于phead的时候,它(phead)的前不就是链表的尾部吗,那么理所应当,尾插也可以这样完成:

List.c 文件:2.0

  1. //尾插2.0
  2. void ListPushBack(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);
  5. ListInsert(phead, x);
  6. }

头插

思路:

前面我们已经学习了在pos前插入数据,那么头插的实现就尤为简单了,当pos为原第一个数据phead->next时,此时就是在其之前插入数据,那么实现的不久是头插吗,如下:

List.h 文件:

  1. //头插
  2. void ListPushFront(LTNode* phead, LTDataType x);

List.c 文件:

  1. //头插
  2. void ListPushFront(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);
  5. ListInsert(phead->next, x);
  6. }

Test.c 文件:

  1. void TestList4()
  2. {
  3. LTNode* pList = ListInit();
  4. for (int i = 1; i <= 7; i++)
  5. {
  6. ListPushBack(pList, i); //尾插7个数字
  7. }
  8. ListPrint(pList);//打印
  9. for (int i = -2; i <= 0; i++)
  10. {
  11. ListPushFront(pList, i); //头插3个数字
  12. }
  13. ListPrint(pList);//打印
  14. }

效果如下:

3.2、删除pos处节点数据

思路:

删除pos处数据其实也很简单,有点类似于把pos处直接忽略的思想,或者说是绕过去。首先,需要找到pos的上一个节点prev和下一个节点next,将prev和next互相链接即可,直接跳过了pos,这样就实现了删除pos处节点的数据,记得把pos处给free释放掉。这里我们以pos为2示例:

 List.h 文件:

  1. //删除pos处数据
  2. void ListErase(LTNode* pos);

List.c 文件:

  1. //删除pos处数据
  2. void ListErase(LTNode* pos)
  3. {
  4. assert(pos);
  5. //定义两个指针保存pos两边的节点
  6. LTNode* prev = pos->prev;
  7. LTNode* next = pos->next;
  8. //将prev和next链接起来
  9. prev->next = next;
  10. next->prev = prev;
  11. //free释放
  12. free(pos);
  13. pos = NULL;
  14. }

Test.c 文件:

  1. void TestList5()
  2. {
  3. LTNode* pList = ListInit();
  4. for (int i = 1; i <= 7; i++)
  5. {
  6. ListPushBack(pList, i); //尾插7个数据
  7. }
  8. ListPrint(pList);//打印尾插的7个
  9. //寻找数字
  10. LTNode* pos = ListFind(pList, 3);
  11. if (pos)
  12. {
  13. ListErase(pos); //删除pos处数据
  14. pos = NULL; //形参的改变不会影响实参,最好在这置空pos
  15. }
  16. ListPrint(pList);//打印
  17. }

效果如下:

尾删

思路:

双向循环链表的特点将再次得以体现,根据其特性,我们知道phead的prev指向尾节点,用tail指针保存,再定义一个指针tailPrev指向tail的prev,现仅需将tailPrev的next指向哨兵位节点phead,再把哨兵位phead的prev重新置成tailPrev即可,但是别忘记把删掉的尾节点给释放掉,得free(tail)。记得要断言链表不能为空,因为不能删除哨兵位节点。

List.H 文件:

  1. //尾删
  2. void ListPopBack(LTNode* phead);

List.c 文件:1.0

  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. //释放尾节点
  9. free(tail);
  10. tail = NULL;
  11. //将链表循环起来
  12. tailPrev->next = phead;
  13. phead->prev = tailPrev;
  14. }

Test.c 文件:

  1. void TestList2()
  2. {
  3. LTNode* pList = ListInit();
  4. for (int i = 1; i <= 7; i++)
  5. {
  6. ListPushBack(pList, i); //尾插7个数据
  7. }
  8. ListPrint(pList);//打印尾插的7个
  9. //尾删两次
  10. ListPopBack(pList);
  11. ListPopBack(pList);
  12. ListPrint(pList);//再次打印
  13. }

效果如下:

 注意:

前文我们已经学了删除pos处节点的数据,那么当pos为phead->prev时,删除的是不是就是尾节点,所以,尾删理所应当可以这样写:

List.c 文件:2.0

  1. //尾删
  2. void ListPopBack(LTNode* phead)
  3. {
  4. assert(phead);//本身就有哨兵位,不能为空,要断言
  5. assert(phead->next != phead); //防止链表为空,导致删除哨兵位节点
  6. ListErase(phead->prev);
  7. }

头删

思路:

有了上文之鉴,我们可以直接利用前面写的删除pos处数据的函数来完成,当pos为phead->prev时,pos的位置就是尾,此时删除的就是尾。当然还得注意一点,需要额外assert断言防止删除的数据为哨兵位的节点。

List.h 文件:

  1. //头删
  2. void ListPopFront(LTNode* phead);

List.c 文件:

  1. //头删
  2. void ListPopFront(LTNode* phead)
  3. {
  4. assert(phead);
  5. assert(phead->next != phead); //防止删除哨兵位节点
  6. ListErase(phead->next);
  7. }

Test.c 文件:

  1. void TestList6()
  2. {
  3. LTNode* pList = ListInit();
  4. for (int i = 1; i <= 7; i++)
  5. {
  6. ListPushBack(pList, i); //尾插7个数字
  7. }
  8. ListPrint(pList);//打印
  9. //头插3个数字
  10. ListPushFront(pList, 0);
  11. ListPushFront(pList, -1);
  12. ListPushFront(pList, -2);
  13. ListPrint(pList);//打印
  14. //尾删3个数字
  15. ListPopBack(pList);
  16. ListPopBack(pList);
  17. ListPopBack(pList);
  18. ListPrint(pList);//打印
  19. //头删3个数字
  20. ListPopFront(pList);
  21. ListPopFront(pList);
  22. ListPopFront(pList);
  23. ListPrint(pList);//打印
  24. }

效果如下:

3.3、查找数据

思路:

查找数据其实也比较简单,首先,定义一个指针cur指向哨兵位phead的next,依次遍历cur看cur->data是否为查找的数据x,如果是,则返回cur,否则继续(cur=cur->next),若找不到则返回NULL。

List.h 文件:

  1. //链表查找
  2. LTNode* ListFind(LTNode* phead, LTDataType x);

List.c 文件:

  1. //链表查找
  2. LTNode* ListFind(LTNode* phead, LTDataType x)
  3. {
  4. assert(phead);
  5. LTNode* cur = phead->next;
  6. while (cur != phead)
  7. {
  8. if (cur->data == x)
  9. {
  10. return cur; //找到就返回cur
  11. }
  12. cur = cur->next;
  13. }
  14. return NULL; //找不到就返回空
  15. }

四、总代码

List.h 文件

  1. #pragma once
  2. #include<stdio.h>
  3. #include<assert.h>
  4. #include<stdlib.h>
  5. //创建双向链表结构
  6. typedef int LTDataType; //方便后续更改数据类型,本文以int整型为主
  7. typedef struct ListNode
  8. {
  9. LTDataType data; //存储数据
  10. struct ListNode* next; //指向下一个
  11. struct ListNode* prev; //指向上一个
  12. }LTNode; //方便后续使用,不需要重复些struct
  13. //初始化链表(二级指针版本)
  14. /*void ListInit(LTNode** pphead);*/
  15. //初始化链表(一级指针版本)
  16. LTNode* ListInit();
  17. //打印链表
  18. void ListPrint(LTNode* phead);
  19. //链表查找
  20. LTNode* ListFind(LTNode* phead, LTDataType x);
  21. //销毁链表
  22. void ListDestory(LTNode* phead);
  23. //尾插
  24. void ListPushBack(LTNode* phead, LTDataType x);
  25. //尾删
  26. void ListPopBack(LTNode* phead);
  27. //头插
  28. void ListPushFront(LTNode* phead, LTDataType x);
  29. //头删
  30. void ListPopFront(LTNode* phead);
  31. //在pos前插入数据
  32. void ListInsert(LTNode* pos, LTDataType x);
  33. //删除pos处数据
  34. void ListErase(LTNode* pos);

List.c 文件

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"List.h"
  3. //创建新节点
  4. LTNode* BuyLTNode(LTDataType x)
  5. {
  6. LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
  7. if (newnode == NULL)
  8. {
  9. printf("malloc fail\n");
  10. exit(-1);
  11. }
  12. newnode->data = x;
  13. newnode->next = NULL;
  14. newnode->prev = NULL;
  15. return newnode; //返回新创建的节点
  16. }
  17. //初始化链表(二级指针版)
  18. /*void ListInit(LTNode** pphead)
  19. {
  20. //传二级指针,那么当然要断言
  21. assert(pphead);
  22. *pphead = BuyLTNode(0);//因为是带哨兵位的头节点,所以一开始就要给一个节点
  23. //为了循环,要让哨兵位的next和prev均指向自己
  24. (*pphead)->next = *pphead; //注意优先级,*pphead要加括号
  25. (*pphead)->prev = *pphead;
  26. }*/
  27. //初始化链表(一级指针版)
  28. LTNode* ListInit()
  29. {
  30. LTNode* phead = BuyLTNode(0);
  31. phead->next = phead;
  32. phead->prev = phead;
  33. return phead;
  34. }
  35. //打印链表
  36. void ListPrint(LTNode* phead)
  37. {
  38. assert(phead);//断言
  39. LTNode* cur = phead->next;
  40. while (cur != phead)
  41. {
  42. printf("%d ", cur->data);
  43. cur = cur->next;
  44. }
  45. printf("\n");
  46. }
  47. //链表查找
  48. LTNode* ListFind(LTNode* phead, LTDataType x)
  49. {
  50. assert(phead);
  51. LTNode* cur = phead->next;
  52. while (cur != phead)
  53. {
  54. if (cur->data == x)
  55. {
  56. return cur; //找到就返回cur
  57. }
  58. cur = cur->next;
  59. }
  60. return NULL; //找不到就返回空
  61. }
  62. //销毁链表
  63. void ListDestory(LTNode* phead)
  64. {
  65. assert(phead);
  66. LTNode* cur = phead->next;
  67. //销毁从第一个节点到尾部的数据
  68. while (cur != phead)
  69. {
  70. LTNode* next = cur->next;
  71. //ListErase(cur);
  72. free(cur);
  73. cur = next;
  74. }
  75. //置空哨兵位节点phead
  76. free(phead);
  77. phead = NULL;
  78. }
  79. //尾插
  80. void ListPushBack(LTNode* phead, LTDataType x)
  81. {
  82. assert(phead); //断言,防止头节点为空
  83. /*
  84. 法一:
  85. LTNode* tail = phead->prev; //找到尾节点,便于后续插入数据
  86. LTNode* newnode = BuyLTNode(x);//创建新节点
  87. //将此新插入的尾节点与上一个节点链接起来
  88. tail->next = newnode;
  89. newnode->prev = tail;
  90. //将尾节点与哨兵位phead链接起来构成循环
  91. newnode->next = phead;
  92. phead->prev = newnode;
  93. */
  94. //法二:
  95. ListInsert(phead, x);
  96. }
  97. //尾删
  98. void ListPopBack(LTNode* phead)
  99. {
  100. assert(phead);//本身就有哨兵位,不能为空,要断言
  101. assert(phead->next != phead); //防止链表为空,导致删除哨兵位节点
  102. /*
  103. 法一:
  104. LTNode* tail = phead->prev;
  105. LTNode* tailPrev = tail->prev;
  106. //释放尾节点
  107. free(tail);
  108. tail = NULL;
  109. //将链表循环起来
  110. tailPrev->next = phead;
  111. phead->prev = tailPrev;
  112. */
  113. //法二:
  114. ListErase(phead->prev);
  115. }
  116. //头插
  117. void ListPushFront(LTNode* phead, LTDataType x)
  118. {
  119. assert(phead);
  120. ListInsert(phead->next, x);
  121. }
  122. //头删
  123. void ListPopFront(LTNode* phead)
  124. {
  125. assert(phead);
  126. assert(phead->next != phead); //防止删除哨兵位节点
  127. ListErase(phead->next);
  128. }
  129. //在pos前插入数据
  130. void ListInsert(LTNode* pos, LTDataType x)
  131. {
  132. assert(pos);
  133. //创建插入数据的新节点
  134. LTNode* newnode = BuyLTNode(x);
  135. //链接左侧
  136. pos->prev->next = newnode;
  137. newnode->prev = pos->prev;
  138. //链接右侧
  139. newnode->next = pos;
  140. pos->prev = newnode;
  141. }
  142. //删除pos处数据
  143. void ListErase(LTNode* pos)
  144. {
  145. assert(pos);
  146. //定义两个指针保存pos两边的节点
  147. LTNode* prev = pos->prev;
  148. LTNode* next = pos->next;
  149. //将prev和next链接起来
  150. prev->next = next;
  151. next->prev = prev;
  152. //free释放
  153. free(pos);
  154. pos = NULL;
  155. }

Test.c 文件

  1. #define _CRT_SECURE_NO_WARNINGS 1
  2. #include"List.h"
  3. void TestList1()
  4. {
  5. //初始化(法一)
  6. /*LTNode* pList = NULL;
  7. ListInit(&pList);*/
  8. //初始化(法二)
  9. LTNode* pList = ListInit();
  10. for (int i = 1; i <= 7; i++)
  11. {
  12. ListPushBack(pList, i); //尾插7个数据
  13. }
  14. ListPrint(pList);//打印尾插的7个
  15. }
  16. void TestList2()
  17. {
  18. LTNode* pList = ListInit();
  19. for (int i = 1; i <= 7; i++)
  20. {
  21. ListPushBack(pList, i); //尾插7个数据
  22. }
  23. ListPrint(pList);//打印尾插的7个
  24. //尾删两次
  25. ListPopBack(pList);
  26. ListPopBack(pList);
  27. ListPrint(pList);//再次打印
  28. }
  29. void TestList3()
  30. {
  31. LTNode* pList = ListInit();
  32. for (int i = 1; i <= 7; i++)
  33. {
  34. ListPushBack(pList, i); //尾插7个数据
  35. }
  36. ListPrint(pList);//打印尾插的7个
  37. //寻找数字
  38. LTNode* pos = ListFind(pList, 3);
  39. if (pos)
  40. {
  41. ListInsert(pos, 30); //找到数字3就插入
  42. }
  43. ListPrint(pList);//打印
  44. }
  45. void TestList4()
  46. {
  47. LTNode* pList = ListInit();
  48. for (int i = 1; i <= 7; i++)
  49. {
  50. ListPushBack(pList, i); //尾插7个数字
  51. }
  52. ListPrint(pList);//打印
  53. for (int i = -2; i <= 0; i++)
  54. {
  55. ListPushFront(pList, i); //头插3个数字
  56. }
  57. ListPrint(pList);//打印
  58. }
  59. void TestList5()
  60. {
  61. LTNode* pList = ListInit();
  62. for (int i = 1; i <= 7; i++)
  63. {
  64. ListPushBack(pList, i); //尾插7个数据
  65. }
  66. ListPrint(pList);//打印尾插的7个
  67. //寻找数字
  68. LTNode* pos = ListFind(pList, 3);
  69. if (pos)
  70. {
  71. ListErase(pos); //删除pos处数据
  72. pos = NULL; //形参的改变不会影响实参,最好在这置空pos
  73. }
  74. ListPrint(pList);//打印
  75. }
  76. void TestList6()
  77. {
  78. LTNode* pList = ListInit();
  79. for (int i = 1; i <= 7; i++)
  80. {
  81. ListPushBack(pList, i); //尾插7个数字
  82. }
  83. ListPrint(pList);//打印
  84. //头插3个数字
  85. ListPushFront(pList, 0);
  86. ListPushFront(pList, -1);
  87. ListPushFront(pList, -2);
  88. ListPrint(pList);//打印
  89. //尾删3个数字
  90. ListPopBack(pList);
  91. ListPopBack(pList);
  92. ListPopBack(pList);
  93. ListPrint(pList);//打印
  94. //头删3个数字
  95. ListPopFront(pList);
  96. ListPopFront(pList);
  97. ListPopFront(pList);
  98. ListPrint(pList);//打印
  99. //销毁链表
  100. ListDestory(pList);
  101. pList = NULL;
  102. }
  103. void TestList7()
  104. {
  105. LTNode* pList = ListInit();
  106. for (int i = 1; i <= 7; i++)
  107. {
  108. ListPushBack(pList, i); //尾插7个数字
  109. }
  110. ListPrint(pList);//打印
  111. //销毁链表
  112. ListDestory(pList);
  113. pList = NULL;
  114. }
  115. int main()
  116. {
  117. //TestList1();
  118. //TestList2();
  119. //TestList3();
  120. //TestList4();
  121. //TestList5();
  122. //TestList6();
  123. TestList7();
  124. return 0;
  125. }

五、拓展

对比顺序表和链表

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

优缺点对比:

 顺序表链表
优点

1、物理空间是连续的,方便用下标随机访问。

2、CPU高速缓存命中率会更高。(补充)

1、按需申请释放空间。

2、任意位置可以O(1)插入删除数据。

缺点

1、正因为物理空间连续,空间不够需要扩容,扩容本身又一定消耗,其次扩容机制还存在一定的空间浪费。

2、头部或者中部插入删除,挪动数据,效率低,O(N)。

1、不支持下标的随机访问。

2、有些算法不适合在它上面进行,如:二分查找、排序等。

到此这篇关于C语言超详细讲解数据结构中双向带头循环链表的文章就介绍到这了,更多相关C语言 双向带头循环链表内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号