当前位置:   article > 正文

C语言初阶数据结构(三)双向链表(保姆级别详细图解,轻松上手,通俗易懂)_c语言双向链表

c语言双向链表

目录

一、基本结构

二、功能实现以及效果展示

2.1创建结构体变量  

2.2创建新结点

2.3初始化链表

2.4打印结点数据

2.5头插数据

2.6尾插数据

2.7头删数据

2.8尾删数据

2.9查找数据

2.10随机插入数据

2.11随机删除数据

2.12计算链表元素个数

2.13销毁链表

三、完整代码


、完整代码


一、基本结构

首先要明白双向链表里面包含什么:“双向”两个方向,用两个指针,一个头指针Prev和一个尾指针next ,如下图结点:

头结点不包含数据,后续结点用data存放数据

二、功能实现以及效果展示

菜单展示:

 1.创建结构体变量,成员变量包含头结点prev和数据data以及尾指针next

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

 2.创建新结点newnode,返回newnode的地址

使用malloc创建新结点,然后使其头指针和为指针为NULL

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

3.初始化双向链表

为了避免使用二级指针,可以在初始化双向链表时,返回头结点的地址。让ListNode*类型的指针plist存放头结点的地址

ListNode* plist = ListInit();

初始化双向链表:

  1. ListNode* ListInit()
  2. {
  3. ListNode* phead = BuyListNode(0);
  4. phead->next = phead;
  5. phead->prev = phead;
  6. return phead;
  7. }

要让头结点的头指针和尾指针指向自己

4.打印结点数据

定义一个指针cur,用来指向头结点的后继节点,然后从该位置开始打印,每次打印完该结点的数据后,就使指针cur指向下一个结点,直到cur指向的结点是头结点时结束

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

5.头插数据

   定义头结点的后继节点为First,这样就避免了在插入数据时考虑指针顺序,创建一个新结点newnode,然后让newnode的尾结点指向First,再让First的头指针指向newnode,再连接newnode和头结点即可。

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

头插法以及打印数据效果展示

 6.尾插数据

  头结点的前驱结点就是尾结点,所以,直接定义一个指向头结点的指针tail 然后创建一个新结点,存放要插入的数据,直接将newnode和tail 以及  newnode和头结点相连

  1. void ListPushBack(ListNode* phead,LTDataType x)
  2. {
  3. //头结点phead的前驱结点就是尾结点
  4. assert(phead);
  5. ListNode* tail = phead->prev;
  6. ListNode*newnode= BuyListNode(x);
  7. newnode->prev = tail;
  8. tail->next = newnode;
  9. phead->prev = newnode;
  10. newnode->next = phead;
  11. /*ListInsert(phead, x);*/
  12. }

 尾插数据效果展示

7.头删数据

  创建一个指针Second,让该指针先存放第二个结点的地址,避免第一个结点Frist被释放后,第二个结点找不到,先让头结点的尾结点指向Second,然后让Second的头指针指向头结点,然后释放第一个结点First

  1. void ListPopFront(ListNode* phead)
  2. {
  3. assert(phead);
  4. assert(phead->next != phead);
  5. ListNode* First = phead->next;
  6. ListNode* Second = First->next;
  7. phead->next = Second;
  8. Second->prev = phead;
  9. free(First);
  10. First = NULL;
  11. //ListErase(phead->next);
  12. }

头删数据效果展示

8.尾删数据

定义一个指针tail存放头结点的前驱结点也就是尾结点的地址,然后找到tail的前驱结点prev,然后将prev的尾指针指向头结点phead,然后phead的头指针指向prev,释放掉tail即可

  1. void ListPopBack(ListNode* phead)
  2. {
  3. assert(phead);
  4. assert(phead->next != phead);
  5. ListNode* tail = phead->prev;
  6. ListNode* prev = tail->prev;
  7. prev->next = phead;
  8. phead->prev = prev;
  9. free(tail);
  10. tail = NULL;
  11. /*ListErase(phead->prev);*/
  12. }

尾插数据效果展示

9.查找数据(效果展示与10和11搭配实现)

定义一个指针cur从第一个结点(非头结点)开始查询,如果找到了,就返回该结点的地址,如果找不到就返回NULL

  1. ListNode* ListFind(ListNode* phead, LTDataType x)
  2. {
  3. assert(phead);
  4. ListNode* 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. }

10.随机插入数据

一般与查找数据连用,定义一个指针pos接受查询到的数据的地址,然后再pos指向的结点的前面插入一个结点,创建一个新结点newnode,存放要插入的数据,然后找到pos的前驱结点prev,使prev的尾结点指向newnode,newnode的头结点指向prev,然后再将newnode跟pos相连

这里的data那里应该是数据x,之前的应该也是,画到一半才想起来QAQ

 其实细心的UU可以观察到,头插数据和尾插数据都可以调用随机插入函数,只是头插数据的时候pos是phead->next     尾插数据时pos就是phead

头插    ListInsert(phead->next, x);

尾插    ListInsert(phead, x);

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

随机插入效果展示

11.随机删除数据

定义一个pos指针接受查询到的数据的地址,然后在定义一个指针prev指向pos的前驱结点,定义一个指针next指向pos的后继结点,然后让prev的尾指针指向next,再让next的头指针指向prev,最后释放掉pos即可

 其实头删数据、尾删数据都可以通过随机删除实现

头删数据:ListErase(phead->next)

尾删数据:Li

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

stErase(phead->prev);

随机删除效果展示

12.修改数据 

先找到想要修改的数据,然后再将要修改的数据输入即可

  1. printf("请输入你想要修改的数据:\n");
  2. scanf("%d", &x);
  3. ListNode * pos2 = ListFind(plist, x);
  4. if (pos2 == NULL)
  5. {
  6. printf("该数据不存在,请重新进行操作!\n");
  7. }
  8. else
  9. {
  10. printf("该数据存在,请输入数据进行修改:\n");
  11. scanf("%d", &x);
  12. pos2->data = x;
  13. printf("修改成功!\n");
  14. }
  15. break;

效果展示

13.计算链表数据个数

定义一个指针cur指向头结点的后继结点,定义一个变量count计算数据个数,让cur依次向后移动,每次让count++;直到cur指向头结点时结束

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

效果展示

14.销毁双向链表 

定义一个指针cur指向第一个结点,然后从第一个结点开始释放,每次定义一个指针next指向cur的后一个结点,然后将cur释放,再将next的地址给cur,直到只剩下头结点,最后将头结点也释放

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

如有问题评论就好,看到就会回嗷~   UU们如有帮助,点点赞呐~

头文件:List.h(函数声明)

  1. //头文件
  2. #pragma once
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<assert.h>
  6. typedef int LTDataType;
  7. typedef struct ListNode
  8. {
  9. struct ListNode* prev;
  10. struct ListNode* next;
  11. LTDataType data;
  12. }ListNode;
  13. //初始化双向链表
  14. ListNode* ListInit();
  15. //打印双向链表
  16. void ListPrint(ListNode* phead);
  17. //销毁双向链表
  18. void ListDestory(ListNode* phead);
  19. //尾插法
  20. void ListPushBack(ListNode* phead, LTDataType x);
  21. //头插法
  22. void ListPushFront(ListNode* phead, LTDataType x);
  23. //尾删
  24. void ListPopBack(ListNode* phead);
  25. //头删
  26. void ListPopFront(ListNode* phead);
  27. //查找数据
  28. ListNode*ListFind(ListNode* phead, LTDataType x);
  29. //随机插入数据
  30. void ListInsert(ListNode* pos, LTDataType x);
  31. //随机删除数据
  32. void ListErase(ListNode* pos);
  33. //计算双向链表中的数据个数
  34. int ListSize(ListNode* phead);

源文件List.c(函数功能实现)

  1. #include"List.h"
  2. //创建新结点
  3. ListNode* BuyListNode(LTDataType x)
  4. {
  5. ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
  6. if (newnode == NULL)
  7. {
  8. perror("");
  9. return;
  10. }
  11. else
  12. {
  13. newnode->data = x;
  14. newnode->next = NULL;
  15. newnode->prev = NULL;
  16. return newnode;
  17. }
  18. }
  19. //初始化双向链表
  20. ListNode* ListInit()
  21. {
  22. ListNode* phead = BuyListNode(0);
  23. phead->next = phead;
  24. phead->prev = phead;
  25. return phead;
  26. }
  27. //打印双向链表
  28. void ListPrint(ListNode* phead)
  29. {
  30. ListNode* cur = phead->next;
  31. while (cur != phead)
  32. {
  33. printf("%d ", cur->data);
  34. cur = cur->next;
  35. }
  36. printf("\n");
  37. }
  38. //尾插法
  39. void ListPushBack(ListNode* phead,LTDataType x)
  40. {
  41. //头结点phead的前驱结点就是尾结点
  42. assert(phead);
  43. ListNode* tail = phead->prev;
  44. ListNode*newnode= BuyListNode(x);
  45. newnode->prev = tail;
  46. tail->next = newnode;
  47. phead->prev = newnode;
  48. newnode->next = phead;
  49. /*ListInsert(phead, x);*/
  50. }
  51. //头插法
  52. void ListPushFront(ListNode* phead, LTDataType x)
  53. {
  54. assert(phead);
  55. ListNode* First = phead->next;
  56. ListNode*newnode= BuyListNode(x);
  57. phead->next = newnode;
  58. newnode->prev = phead;
  59. newnode->next = First;
  60. First->prev = newnode;
  61. /*ListInsert(phead->next, x);*/
  62. }
  63. //尾删
  64. void ListPopBack(ListNode* phead)
  65. {
  66. assert(phead);
  67. assert(phead->next != phead);
  68. ListNode* tail = phead->prev;
  69. ListNode* prev = tail->prev;
  70. prev->next = phead;
  71. phead->prev = prev;
  72. free(tail);
  73. tail = NULL;
  74. /*ListErase(phead->prev);*/
  75. }
  76. //头删
  77. void ListPopFront(ListNode* phead)
  78. {
  79. assert(phead);
  80. assert(phead->next != phead);
  81. ListNode* First = phead->next;
  82. ListNode* Second = First->next;
  83. phead->next = Second;
  84. Second->prev = phead;
  85. free(First);
  86. First = NULL;
  87. //ListErase(phead->next);
  88. }
  89. //查找数据
  90. ListNode* ListFind(ListNode* phead, LTDataType x)
  91. {
  92. assert(phead);
  93. ListNode* cur = phead->next;
  94. while (cur != phead)
  95. {
  96. if (cur->data == x)
  97. {
  98. return cur;
  99. }
  100. cur = cur->next;
  101. }
  102. return NULL;
  103. }
  104. //随机插入数据 在pos的前面插入数据
  105. void ListInsert(ListNode* pos, LTDataType x)
  106. {
  107. assert(pos);
  108. ListNode* newnode = BuyListNode(x);
  109. ListNode* prev = pos->prev;
  110. prev->next = newnode;
  111. newnode->prev = prev;
  112. newnode->next = pos;
  113. pos->prev = newnode;
  114. }
  115. //随机删除数据 删除pos位置的数据
  116. void ListErase(ListNode* pos)
  117. {
  118. assert(pos);
  119. ListNode* prev = pos->prev;
  120. ListNode* next = pos->next;
  121. prev->next = next;
  122. next->prev = prev;
  123. free(pos);
  124. pos = NULL;
  125. }
  126. //计算双向链表中的元素个数
  127. int ListSize(ListNode* phead)
  128. {
  129. assert(phead);
  130. ListNode* cur = phead->next;
  131. int count = 0;
  132. while (cur != phead)
  133. {
  134. count++;
  135. cur = cur->next;
  136. }
  137. return count;
  138. }
  139. //销毁双向链表
  140. void ListDestory(ListNode* phead)
  141. {
  142. assert(phead);
  143. ListNode* cur = phead->next;
  144. while (cur != phead)
  145. {
  146. ListNode* next = cur->next;
  147. free(cur);
  148. cur = NULL;
  149. cur = next;
  150. }
  151. free(phead);
  152. phead = NULL;
  153. }

 源文件text.c(功能测试)

  1. #include"List.h"
  2. void menu()
  3. {
  4. printf("*******************************************\n");
  5. printf("**1.尾插数据 2.头插数据\n");
  6. printf("**3.尾删数据 4.头删数据\n");
  7. printf("**5.随机插入 6.随机删除\n");
  8. printf("**7.修改数据 8.求结点个数\n");
  9. printf("**9.打印链表 0.退出程序\n");
  10. printf("*******************************************\n");
  11. }
  12. void text()
  13. {
  14. ListNode* plist = ListInit();
  15. int input = 0;
  16. int i = 0;
  17. LTDataType x = 0;
  18. do
  19. {
  20. menu();
  21. scanf("%d", &input);
  22. switch (input)
  23. {
  24. case 1:
  25. printf("请输入你想插入的数据,以-1结束\n");
  26. do
  27. {
  28. scanf("%d", &x);
  29. if (x != -1)
  30. {
  31. ListPushBack(plist, x);
  32. }
  33. } while (x != -1);
  34. printf("插入成功\n");
  35. break;
  36. case 2:
  37. printf("请输入你想插入的数据,以-1结束\n");
  38. do
  39. {
  40. scanf("%d", &x);
  41. if (x != -1)
  42. {
  43. ListPushFront(plist, x);
  44. }
  45. } while (x != -1);
  46. printf("插入成功\n");
  47. break;
  48. case 3:
  49. ListPopBack(plist);
  50. printf("删除成功!\n");
  51. break;
  52. case 4:
  53. ListPopFront(plist);
  54. printf("删除成功!\n");
  55. break;
  56. case 5:
  57. printf("请先输入你要插入的位置的当前数据:\n");
  58. scanf("%d", &x);
  59. ListNode* pos = ListFind(plist, x);
  60. if (pos == NULL)
  61. {
  62. printf("该数据不存在,请重新选择随机插入选项进行操作!\n");
  63. }
  64. else
  65. {
  66. printf("该数据存在,请输入您要插入的数据:\n");
  67. scanf("%d", &x);
  68. ListInsert(pos, x);
  69. printf("插入成功!\n");
  70. }
  71. break;
  72. case 6:
  73. printf("请先输入你要删除的位置的当前数据:\n");
  74. scanf("%d", &x);
  75. ListNode* pos1 = ListFind(plist, x);
  76. if (pos1 == NULL)
  77. {
  78. printf("该数据不存在,请重新选择随机删除选项进行操作!\n");
  79. }
  80. else
  81. {
  82. ListErase(pos1);
  83. printf("删除成功!\n");
  84. }
  85. break;
  86. case 7:
  87. printf("请输入你想要修改的数据:\n");
  88. scanf("%d", &x);
  89. ListNode * pos2 = ListFind(plist, x);
  90. if (pos2 == NULL)
  91. {
  92. printf("该数据不存在,请重新进行操作!\n");
  93. }
  94. else
  95. {
  96. printf("该数据存在,请输入数据进行修改:\n");
  97. scanf("%d", &x);
  98. pos2->data = x;
  99. printf("修改成功!\n");
  100. }
  101. break;
  102. case 8:
  103. i=ListSize(plist);
  104. printf("结点个数为%d个\n", i);
  105. break;
  106. case 9:
  107. ListPrint(plist);
  108. break;
  109. case 0:
  110. printf("退出程序!\n");
  111. break;
  112. default:
  113. printf("输入的数字有误,请重新选择\n");
  114. break;
  115. }
  116. } while (input);
  117. }
  118. int main()
  119. {
  120. text();
  121. return 0;
  122. }

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

闽ICP备14008679号