数据结构(5)线性表之双向链表

╰+哭是因爲堅強的太久メ 2023-06-04 06:55 256阅读 0赞

数据结构(5)线性表之双向链表

      • 前言
      • 增加双向链表的管理结构
      • 双向链表的插入
        • 尾部插入
        • 头部与按值插入
      • 双向链表的删除
      • 排序与逆置
      • 全部代码
        • DList.h
        • DList.cpp
        • Main.cpp

前言

在先前讨论的链式存储结构中,无论是单链表还是单循环链表,都只有一个指向后继的指针,因此可以很容易找到它的后继,但是要找它的前驱却很麻烦。那么可不可以再增加一个指向前驱的指针,这样在遍历链表时不仅能从前往后遍历,也能从后往前遍历呢?这就是我们今天所说的双向链表。

顾名思义,双向链表就是其结点中有两个指针域,一个指向直接前驱,一个指向直接后继。当然,链表的第一个结点无直接前驱,最后一个结点无直接后继,设置为空即可。

img\_1

实现双向链表的相关操作,思路上同单链表是一致的,唯一的区别是还需要考虑Prior指针的指向问题。

增加双向链表的管理结构

我们同样为双向链表设置管理结构,first指针指向头结点,last指针指向最后一个结点,size保存链表结点的数量。同单链表一样,在增加了管理结构之后,在做插入、删除操作时,除了保证链表本身的正确性,还要考虑到管理结构的正确性,也就是注意first和last指针指向的正确,和size的数量正确。

img\_2

双向链表的插入

与单链表不同的是,双向链表的改动除了涉及Next指针外,还涉及Prior指针,因此每当我们要执行插入操作时,一般要考虑四个指针的指向

  • 待插入结点的Prior指针
  • 待插入结点的Next指针
  • 待插入结点前驱的Next指针
  • 待插入结点后继的Prior指针

img\_3

只要想好了这四个指针的指向问题,双向链表的操作也就没什么难点了

尾部插入

尾部插入只涉及两个结点,因此只需要考虑两个指针,以及管理结构的last指针即可

img\_4

头部与按值插入

头部插入是直接插在头结点后的位置,按值插入是找到插入位置后直接进行插入(如果是最后一位直接进行尾部插入),两种方式都需要考虑四个指针的指向

同时,在设置四个指针指向的时候要注意先后的顺序,避免出现指针丢失的问题

img\_5

双向链表的删除

双向链表的删除比单链表的删除方便,因为可以找到直接前驱,所以也不需要使用修改值的方法了,直接修改直接前驱和直接后继的指针就好了。尾部、头部和按值删除的方法都类似,只是涉及到的指针有区别,不详谈了。

img\_6

排序与逆置

与单链表的思路一样,在进行双向链表的排序时,可以借鉴按值插入的思路,将原有的双向链表断开,再遍历一个个取出结点,进行按值插入。而在进行逆置时,则进行头部插入即可。

在这里我是利用了已经写好的方法,直接调用方法进行按值插入,再将原来的结点释放掉。

  1. while (q != NULL) {
  2. //取出后续结点
  3. s = q;
  4. q = q->next;
  5. //进行按值插入
  6. insert_val(list, s->data);
  7. //释放原来的结点
  8. free(s);
  9. list->size --;
  10. }

也可以不调用方法,通过设置指针的方式将结点重新连接到链表中

  1. while (q != NULL) {
  2. //取出后续结点
  3. s = q;
  4. q = q->next;
  5. Node *p = list->first;
  6. //寻找插入位置
  7. while (p->next != NULL && p->next->data<s->data) {
  8. p = p->next;
  9. }
  10. //判断是否是最后一个
  11. if (p->next == NULL) {
  12. //是,进行尾部插入
  13. s->next = NULL;
  14. s->prior = list->last;
  15. list->last->next = s;
  16. list->last = s;
  17. }else{
  18. //s的后继指向p的后继
  19. s->next = p->next;
  20. //s的前驱指向p
  21. s->prior = p;
  22. //p后继的前驱指向s
  23. p->next->prior = s;
  24. //p的后继指向s
  25. p->next = s;
  26. }

逆置同理

全部代码

DList.h

  1. #ifndef DList_h
  2. #define DList_h
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <assert.h>
  6. #define ElemType int
  7. typedef struct Node{
  8. ElemType data;
  9. struct Node *prior;
  10. struct Node *next;
  11. }Node,*PNode;
  12. typedef struct List{
  13. PNode first;
  14. PNode last;
  15. int size;
  16. }List;
  17. //初始化双链表
  18. void InitDList(List *list);
  19. //1.尾部插入
  20. void push_back(List *list,ElemType x);
  21. //2.头部插入
  22. void push_fount(List *list,ElemType x);
  23. //3.展示
  24. void show_list(List *list);
  25. //4.尾部删除
  26. void pop_back(List *list);
  27. //5.头部删除
  28. void pop_fount(List *list);
  29. //6.按值插入(要求插入前的链表是有序的(此处为顺序
  30. void insert_val(List *list,ElemType x);
  31. //7.按值查找
  32. Node* find(List *list,ElemType x);
  33. //8.获取长度
  34. int length(List *list);
  35. //9.按值删除
  36. void delete_val(List *list,ElemType x);
  37. //10.排序
  38. void sort(List *list);
  39. //11.逆置(前后转换
  40. void resver(List *list);
  41. //12.清除单链表 ->只清除数据,保留头结点
  42. void clearList(List *list);
  43. //13.摧毁 ->包括头结点在内一起摧毁
  44. void destroy(List *list);
  45. //生成一个结点
  46. Node* getNode(ElemType x);
  47. #endif /* DList_h */

DList.cpp

  1. #include "DList.h"
  2. //初始化
  3. void InitDList(List *list){
  4. Node *s = (Node *)malloc(sizeof(Node));
  5. assert(s != NULL);
  6. list->first = list->last = s;
  7. list->first->next = NULL;
  8. list->first->prior = NULL;
  9. list->size = 0;
  10. }
  11. //1.尾部插入
  12. void push_back(List *list,ElemType x){
  13. //获得一个结点
  14. Node *s = getNode(x);
  15. //s的前驱指向最后一个结点
  16. s->prior = list->last;
  17. //最后一个结点的后继指向s
  18. list->last->next = s;
  19. //重新设置last指针的指向
  20. list->last = s;
  21. list->size ++;
  22. }
  23. //2.头部插入
  24. void push_fount(List *list,ElemType x){
  25. Node *s = getNode(x);
  26. //判断是否是第一个结点
  27. if (list->first == list->last) {
  28. //是第一个结点,s的后继不需要设置
  29. //s的前驱指向头结点
  30. //s->prior = list->first;
  31. //重新设置first指针的指向,使s成为新的首元结点
  32. //list->first->next = s;
  33. //重新设置last指针的指向
  34. list->last = s;
  35. }else{
  36. //s的后继指向首元结点
  37. s->next = list->first->next;
  38. //s的前驱指向头结点
  39. //s->prior = list->first;
  40. //首元结点的前驱指向s
  41. //s->next->prior = s;
  42. list->first->next->prior = s;
  43. //重新设置first指针的指向,使s成为新的首元结点
  44. list->first->next = s;
  45. }
  46. //if-else语句中有重复的部分,可以提出来
  47. //s的前驱指向头结点
  48. s->prior = list->first;
  49. //重新设置first指针的指向,使s成为新的首元结点
  50. list->first->next = s;
  51. list->size ++;
  52. }
  53. //3.展示
  54. void show_list(List *list){
  55. Node *p = list->first->next;
  56. while (p != NULL) {
  57. printf("%4d",p->data);
  58. p = p->next;
  59. }
  60. printf("\n");
  61. }
  62. //4.尾部删除
  63. void pop_back(List *list){
  64. if (list->size == 0) {
  65. printf("链表已空!\n");
  66. return;
  67. }
  68. //方法1
  69. Node *p = list->first;
  70. //循环到链表的最后一位
  71. while (p->next != NULL) {
  72. p = p->next;
  73. }
  74. //将last指针指向倒数第二位(即p的前一位
  75. list->last = p->prior;
  76. //释放该结点的空间
  77. free(p);
  78. //此时链表最后一位的后继为空
  79. list->last->next = NULL;
  80. list->size --;
  81. /* //方法2 Node *p = list->first; //循环到链表倒数第二位 while (p->next != list->last) { p = p->next; } //释放最后一个结点的空间 free(p->next); p->next = NULL; //重新设置last指针的指向 list->last = p; list->size --; */
  82. }
  83. //5.头部删除
  84. void pop_fount(List *list){
  85. if (list->size == 0) {
  86. printf("链表已空!\n");
  87. return;
  88. }
  89. Node *s = list->first->next;
  90. //判断是否是最后一个结点
  91. if (s->next == NULL) {
  92. //是,则s没有后继,不需要修改后继的指针
  93. //修改last指针的指向
  94. list->last = list->first;
  95. list->last->next = NULL;
  96. }else{
  97. //s的后继的prior指针指向头结点
  98. s->next->prior = list->first;
  99. //头结点的next指针指向s的后继
  100. list->first->next = s->next;
  101. }
  102. //释放该结点
  103. free(s);
  104. list->size --;
  105. }
  106. //6.按值插入(要求插入前的链表是有序的(此处为顺序
  107. void insert_val(List *list,ElemType x){
  108. Node *p = list->first;
  109. //寻找插入位置
  110. while (p->next != NULL && p->next->data<x) {
  111. p = p->next;
  112. }
  113. //判断是否是最后一个
  114. if (p->next == NULL) {
  115. //是,直接进行尾部插入
  116. push_back(list, x);
  117. }else{
  118. Node *s = getNode(x);
  119. //s的后继指向p的后继
  120. s->next = p->next;
  121. //s的前驱指向p
  122. s->prior = p;
  123. //p后继的前驱指向s
  124. p->next->prior = s;
  125. //p的后继指向s
  126. p->next = s;
  127. list->size ++;
  128. }
  129. }
  130. //7.按值查找
  131. Node* find(List *list,ElemType x){
  132. Node *p = list->first->next;
  133. while (p != NULL && p->data != x) {
  134. p = p->next;
  135. }
  136. return p;
  137. }
  138. //8.获取长度
  139. int length(List *list){
  140. return list->size;
  141. }
  142. //9.按值删除
  143. void delete_val(List *list,ElemType x){
  144. if (list->size == 0) {
  145. printf("链表已空!\n");
  146. return;
  147. }
  148. Node *p = find(list, x);
  149. if (p == NULL) {
  150. printf("要删除的值不存在!\n");
  151. return;
  152. }
  153. //判断是否是最后一个节点
  154. if (p == list->last) {
  155. //直接进行尾部删除
  156. pop_back(list);
  157. }else{
  158. //p后继的前驱指向p的前驱
  159. p->next->prior = p->prior;
  160. //p前驱的后继指向p的后继
  161. p->prior->next = p->next;
  162. //释放结点空间
  163. free(p);
  164. list->size --;
  165. }
  166. }
  167. //10.排序
  168. void sort(List *list){
  169. if (list->size <= 1) {
  170. //不需要排序
  171. return;
  172. }
  173. Node *s = list->first->next;
  174. Node *q = s->next;
  175. //将链表断开
  176. list->last = s;
  177. list->last->next = NULL;
  178. while (q != NULL) {
  179. //取出后续结点
  180. s = q;
  181. q = q->next;
  182. //进行按值插入
  183. insert_val(list, s->data);
  184. //释放原来的结点
  185. free(s);
  186. list->size --;
  187. // Node *p = list->first;
  188. // //寻找插入位置
  189. // while (p->next != NULL && p->next->data<s->data) {
  190. // p = p->next;
  191. // }
  192. //
  193. // //判断是否是最后一个
  194. // if (p->next == NULL) {
  195. // //是,直接进行尾部插入
  196. // s->next = NULL;
  197. // s->prior = list->last;
  198. // list->last->next = s;
  199. // list->last = s;
  200. // }else{
  201. //
  202. // //s的后继指向p的后继
  203. // s->next = p->next;
  204. // //s的前驱指向p
  205. // s->prior = p;
  206. // //p后继的前驱指向s
  207. // p->next->prior = s;
  208. // //p的后继指向s
  209. // p->next = s;
  210. // }
  211. }
  212. }
  213. //11.逆置(前后转换
  214. void resver(List *list){
  215. if (list->size <= 1) {
  216. //不需要逆置
  217. return;
  218. }
  219. Node *s = list->first->next;
  220. Node *q = s->next;
  221. //将链表断开
  222. list->last = s;
  223. list->last->next = NULL;
  224. while (q != NULL) {
  225. s = q;
  226. q = q->next;
  227. //直接进行头部插入
  228. push_fount(list, s->data);
  229. //释放原有结点
  230. free(s);
  231. list->size --;
  232. }
  233. }
  234. //12.清除单链表 ->只清除数据,保留头结点
  235. void clearList(List *list){
  236. if (list->size == 0) {
  237. printf("链表已空!\n");
  238. return;
  239. }
  240. Node *s = list->first;
  241. Node *p = s->next;
  242. //遍历整个链表
  243. while (p != NULL) {
  244. s = p;
  245. p = p->next;
  246. //释放结点
  247. free(s);
  248. list->size --;
  249. }
  250. list->last = list->first;
  251. list->last->next = NULL;
  252. }
  253. //13.摧毁 ->包括头结点在内一起摧毁
  254. void destroy(List *list){
  255. clearList(list);
  256. free(list->first);
  257. list->first = list->last = NULL;
  258. }
  259. //生成一个结点
  260. Node* getNode(ElemType x){
  261. Node *s = (Node *)malloc(sizeof(Node));
  262. assert(s != NULL);
  263. s->data = x;
  264. s->prior = NULL;
  265. s->next = NULL;
  266. return s;
  267. }

Main.cpp

  1. #include "DList.h"
  2. int main(int argc, const char * argv[]) {
  3. List myList;
  4. InitDList(&myList);
  5. //保存要输入的数据
  6. ElemType item;
  7. //保存要查找的地址
  8. Node *p = NULL;
  9. int select = 1;
  10. while (select) {
  11. printf("**************************************\n");
  12. printf("* [1] push_back [2] push_front *\n");
  13. printf("* [3] show_list [4] pop_back *\n");
  14. printf("* [5] pop_front [6] insert_val *\n");
  15. printf("* [7] find [8] length *\n");
  16. printf("* [9] delete_val [10] sort *\n");
  17. printf("* [11] resver [12] clear *\n");
  18. printf("* [13*] destroy [0] quit_system*\n");
  19. printf("**************************************\n");
  20. printf("请选择:");
  21. scanf("%d",&select);
  22. if (select == 0) {
  23. break;
  24. }
  25. switch (select) {
  26. case 1:
  27. //尾部插入
  28. printf("请输入要插入的数据(-1结束):");
  29. scanf("%d",&item);
  30. while (item != -1) {
  31. push_back(&myList, item);
  32. scanf("%d",&item);
  33. }
  34. break;
  35. case 2:
  36. //头部插入
  37. printf("请输入要插入的数据(-1结束):");
  38. scanf("%d",&item);
  39. while (item != -1) {
  40. push_fount(&myList, item);
  41. scanf("%d",&item);
  42. }
  43. break;
  44. case 3:
  45. //展示单链表
  46. show_list(&myList);
  47. break;
  48. case 4:
  49. //尾部删除
  50. pop_back(&myList);
  51. break;
  52. case 5:
  53. //头部删除
  54. pop_fount(&myList);
  55. break;
  56. case 6:
  57. //按值插入
  58. printf("请输入要插入的数据:\n");
  59. scanf("%d",&item);
  60. insert_val(&myList, item);
  61. break;
  62. case 7:
  63. //按值查找
  64. printf("请输入要查找的值:\n");
  65. scanf("%d",&item);
  66. p = find(&myList, item);
  67. if (p == NULL) {
  68. printf("要查找的数据不存在!\n");
  69. }else{
  70. printf("地址为:%p\n",p);
  71. }
  72. break;
  73. case 8:
  74. //展示链表长度
  75. printf("链表的长度为:%d\n",length(&myList));
  76. break;
  77. case 9:
  78. //按值删除
  79. printf("请输入要删除的值:\n");
  80. scanf("%d",&item);
  81. delete_val(&myList, item);
  82. break;
  83. case 10:
  84. //排序
  85. sort(&myList);
  86. break;
  87. case 11:
  88. //逆置(前后转换
  89. resver(&myList);
  90. break;
  91. case 12:
  92. //清除
  93. clearList(&myList);
  94. break;
  95. case 13:
  96. destroy(&myList);
  97. break;
  98. default:
  99. printf("输入的命令有误,请重新插入\n");
  100. break;
  101. }
  102. }
  103. return 0;
  104. }

发表评论

表情:
评论列表 (有 0 条评论,256人围观)

还没有评论,来说两句吧...

相关阅读