当前位置:   article > 正文

数据结构双向链表_ds双向链表—前驱后继

ds双向链表—前驱后继

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表

那么双向链表相对于单项链表就是可以即向后访问又可以向前访问

那么他的结构相对于单链表就复杂很多了

 

可以看到每一个结点 都有 自己的地址 然后是 指向下一个结点的next域 以及指向上一个地址的prior域

那么在插入删除的时候 我们需要修改更多地方 

例如 需要修改我们的 next prior 以及下一个结点的prior和上一个结点的next

而且我们在修改和使用时要考虑到是否存在 next或者prior

那么我们在结构体设计上就要有所不同

  1. #pragma once
  2. //双向链表的结构体设计:
  3. typedef int ELEM_TYPE;
  4. typedef struct DNode
  5. {
  6. ELEM_TYPE data;//数据域
  7. struct DNode *next;//直接后继指针
  8. struct DNode *prior;//直接前驱指针
  9. }DNode, *PDNode;
  10. 可实现的操作:
  11. //初始化
  12. void Init_dlist(struct DNode * pdlist);
  13. //头插
  14. bool Insert_head(struct DNode *pdlist, ELEM_TYPE val);
  15. //尾插
  16. bool Insert_tail(struct DNode *pdlist, ELEM_TYPE val);
  17. //按位置插
  18. bool Insert_pos(struct DNode *pdlist, int pos, ELEM_TYPE val);
  19. //头删
  20. bool Del_head(struct DNode *pdlist);
  21. //尾删
  22. bool Del_tail(struct DNode *pdlist);
  23. //按位置删
  24. bool Del_pos(struct DNode *pdlist, int pos);
  25. //按值删
  26. bool Del_val(struct DNode *pdlist, ELEM_TYPE val);
  27. //查找 //查找到,返回的是查找到的这个节点的地址
  28. struct DNode *Search(struct DNode *pdlist, ELEM_TYPE val);
  29. //获取有效值个数
  30. int Get_length(struct DNode *pdlist);
  31. //判空
  32. bool IsEmpty(struct DNode *pdlist);
  33. //清空
  34. void Clear(struct DNode *pdlist);
  35. //销毁1 无限头删
  36. void Destroy1(struct DNode *pdlist);
  37. //销毁2 不借助头结点,有两个辅助指针
  38. void Destroy2(struct DNode *pdlist);
  39. //打印
  40. void Show(struct DNode *pdlist);

我们可以看到 结构体设计时我们需要设计 一个数据域 和 两个指针域

然后其他的实现函数的参数和之前学习的单链表没有什么不同 接下来我们重点看实现代码

首先是初始化 

我们只使用next和prior域

 

  1. void Init_dlist(struct DNode * pdlist)
  2. {
  3. assert(pdlist != NULL);
  4. //pdlist->data 头结点的数据域不使用
  5. pdlist->next = NULL;
  6. pdlist->prior = NULL;
  7. }

我们把他的头节点的next和prior赋值为空 

然后是头插

但是头插时我们就要考虑顺序问题和 待插入结点的下一个结点的存在问题

如果我们先修改 待插入结点的上一个结点的next域 那么我们在后续使用时就会因为顺序

产生问题

比如

 因此我们可以调转其他步骤的顺序 但是三号 顺序一定是最后一个

  1. bool Insert_head(struct DNode *pdlist, ELEM_TYPE val)
  2. {
  3. //0.安全性处理
  4. assert(pdlist != NULL);
  5. //1.购买新节点
  6. struct DNode *pnewnode = (struct DNode *)malloc(1 * sizeof(struct DNode));
  7. assert(pnewnode != NULL);
  8. pnewnode->data = val;
  9. //2.找到合适的插入位置(其实就是找到插入在哪一个节点后边,用指针p指向)
  10. //因为是头插,所以直接使用pdlist即可
  11. //3.插入 我们的规则是:1,2,4,3
  12. // 先处理自身的两个指针域(1,2)
  13. // 再处理插入位置的下一个节点的prior域(4),但是4有特例(空链表进行头插),不存在
  14. // 最后处理插入位置的上一个节点的next域(3)
  15. pnewnode->next = pdlist->next;//1
  16. pnewnode->prior = pdlist;//2
  17. if(pdlist->next != NULL)//说明不是空链表,则不是特例,4存在
  18. {
  19. //此时,插入位置的下一个节点可以通过pdlist->next访问到,还可以通过pnewnode->next访问到
  20. pdlist->next->prior = pnewnode;//4
  21. //pnewnode->next->prior = pnewnode;//4
  22. }
  23. pdlist->next = pnewnode;
  24. return NULL;
  25. }

 可以看到 我们实现了第一步和第二步时我们 要判断是否存在四

因为如果不存在四的话 我们对四操作就会变成野指针问题 可能会访问到不能访问的空间 有可能会引发崩溃

然后是尾插

尾插的话 那么它没有 第四步骤 

 

  1. bool Insert_tail(struct DNode *pdlist, ELEM_TYPE val)
  2. {
  3. //0
  4. assert(pdlist != NULL);
  5. //1.购买新节点
  6. struct DNode *pnewnode = (struct DNode *)malloc(1 * sizeof(struct DNode));
  7. assert(pnewnode != NULL);
  8. pnewnode->data = val;
  9. //2.找到合适的插入位置,用指针p指向插入位置的前一个节点
  10. //判断是否使用带前驱的for循环
  11. struct DNode *p = pdlist;
  12. for(; p->next!=NULL; p=p->next);
  13. //3.插入(不存在特殊情况,每一种情况都需要修改三个指针域)
  14. //按照之前编号顺序,修改的这三个指针域分别是1,2,(4不存在),3
  15. pnewnode->next = p->next;//pnewnode->next = NULL;//1
  16. pnewnode->prior = p;//2
  17. //4不存在
  18. p->next = pnewnode;//3
  19. return true;
  20. }

 然后是按位置插入 

只要掌握了插入时的顺序问题 在按照函数功能的要求改变即可

 

  1. bool Insert_pos(struct DNode *pdlist, int pos, ELEM_TYPE val)
  2. {
  3. //因为,在写这个函数之前,头插和尾插已经实现,所以这里可以直接调用
  4. //0.安全性处理
  5. assert(pdlist != NULL);
  6. assert(pos>=0 && pos<=Get_length(pdlist));
  7. //1.分类处理,将头插和尾插的情况,分别调用对应的函数处理
  8. if(pos == 0)//头插
  9. {
  10. return Insert_head(pdlist, val);
  11. }
  12. if(pos == Get_length(pdlist))//尾插
  13. {
  14. return Insert_tail(pdlist, val);
  15. }
  16. //如果既不是头插,也不是尾插,则只有可能是中间插入,1,2,4,3,都存在
  17. //2.剩下来的都是中间位置插入,都正常情况,修改4个指针域
  18. //2.1 购买新节点
  19. struct DNode *pnewnode = (struct DNode *)malloc(1 * sizeof(struct DNode));
  20. assert(pnewnode != NULL);
  21. pnewnode->data = val;
  22. //2.2 找到合适的插入位置,指针p(pos=="几",指针p从头结点出发向后走pos步)
  23. struct DNode *p = pdlist;
  24. for(int i=0; i<pos; i++)
  25. {
  26. p = p->next;
  27. }
  28. //2,3 正常插入,1,2,4,3 都存在,4不需要去判断
  29. pnewnode->next = p->next;//1
  30. pnewnode->prior = p;//2
  31. p->next->prior = pnewnode;//4 pnewnode->next->prior = pnownode;
  32. p->next = pnewnode;//3
  33. return true;
  34. }

 这里需要注意 根据 位置不同 我们可以直接调用 头插 尾插 和普通插入

然后是删除 在插入和删除前我们都需要判断 

但是这里的双向链表没有大小 因此 插入时不需要判满

但是会存在空链表 我们需要在删除时进行 判空操作

  1. bool Del_head(struct DNode *pdlist)
  2. {
  3. //0.安全性处理
  4. assert(pdlist != NULL);
  5. if(IsEmpty(pdlist))
  6. {
  7. return false;
  8. }
  9. //1.用指针p指向待删除节点
  10. struct DNode *p = pdlist->next;
  11. //2.用指针q指向待删除节点的上一个节点
  12. //因为是头删,所以这里指针q用指针pdlist代替
  13. //3.跨越指向(存在特例,正常情况下需要修改两个指针域,而特例时,只需要修改一个指针域)
  14. pdlist->next = p->next;
  15. if(p->next != NULL)//先得判断待删除节点的下一个节点是否存在
  16. {
  17. p->next->prior = pdlist;
  18. }
  19. //4.释放
  20. free(p);
  21. return true;
  22. }
  1. bool IsEmpty(struct DNode *pdlist)
  2. {
  3. return pdlist->next == NULL;
  4. }

 头删时也存在特列 我们头删时

如果只剩下最后一个结点 我们就只需要改变一个地方即可

尾删

 

  1. bool Del_tail(struct DNode *pdlist)
  2. {
  3. //0.
  4. assert(pdlist != NULL);
  5. if(IsEmpty(pdlist))
  6. {
  7. return false;
  8. }
  9. //1.用指针p指向待删除节点
  10. struct DNode *p = pdlist;
  11. for(; p->next!=NULL; p=p->next);
  12. //2.用指针q指向待删除节点的上一个节点
  13. struct DNode *q = pdlist;
  14. for(; q->next!=p; q=q->next);
  15. //3.跨越指向(不存在特例,永远只需要去修改待删除节点的前一个节点的next域)
  16. q->next = p->next;//q->next = NULL;
  17. //4.释放
  18. free(p);
  19. return true;
  20. }

 按位置删除

 

  1. bool Del_pos(struct DNode *pdlist, int pos)
  2. {
  3. //0.安全性处理
  4. assert(pdlist != NULL);
  5. assert(pos >=0 && pos<Get_length(pdlist));
  6. if(IsEmpty(pdlist))
  7. {
  8. return false;
  9. }
  10. //1.分类处理,将头删和尾删的情况,分别调用对应的函数处理
  11. if(pos == 0)//头删
  12. {
  13. return Del_head(pdlist);
  14. }
  15. if(pos == Get_length(pdlist)-1)//尾删
  16. {
  17. return Del_tail(pdlist);
  18. }
  19. //如果既不是头删,也不是尾删,则只有可能是中间删除,则统一需要修改两个指针域
  20. //2.剩下来的都是中间位置删除,统一需要修改两个指针域
  21. //2.1 找到q,让q从头结点开始向后走pos步
  22. struct DNode *q = pdlist;
  23. for(int i=0; i<pos; i++)
  24. {
  25. q = q->next;
  26. }
  27. //2.2 找到p,p=q->next
  28. struct DNode *p = q->next;
  29. //2.3 跨越指向+释放
  30. q->next = p->next;
  31. p->next->prior = q;
  32. free(p);
  33. return true;
  34. }

还有个按值删除 然后 

  1. bool Del_val(struct DNode *pdlist, ELEM_TYPE val)
  2. {
  3. //0.安全性处理
  4. //1.用指针p指向待删除节点,用search函数
  5. struct DNode *p = Search(pdlist, val);
  6. if(p == NULL)
  7. {
  8. return false;
  9. }
  10. //此时,代码执行到这里,可以保证待删除节点存在,且现在用指针p指向
  11. //2.用指针q指向待删除节点的上一个节点
  12. struct DNode *q = pdlist;
  13. for( ; q->next!=p; q=q->next);
  14. //3.跨越指向(有可能存在特例,例如如果待删除节点是尾结点,则只需要处理一个指针域,反之都是两个)
  15. if(p->next == NULL)//判断待删除节点是否是尾结点
  16. {
  17. q->next = NULL;//q->next = p->next;
  18. }
  19. else//如果待删除节点不是尾结点
  20. {
  21. q->next = p->next;
  22. p->next->prior = q;
  23. }
  24. //4.释放
  25. free(p);
  26. return true;
  27. }
  28. //查找 //查找到,返回的是查找到的这个节点的地址
  29. struct DNode *Search(struct DNode *pdlist, ELEM_TYPE val)
  30. {
  31. //0.安全性处理
  32. //1.判断使用哪种for循环
  33. //使用不需要前驱的for循环
  34. struct DNode *p = pdlist->next;
  35. for(; p!=NULL; p=p->next)
  36. {
  37. if(p->data == val)
  38. {
  39. return p;
  40. }
  41. }
  42. return NULL;
  43. }

还有使用 不带头节点的循环获取有效值个数

  1. int Get_length(struct DNode *pdlist)
  2. {
  3. //assert
  4. //使用不需要前驱的for循环
  5. int count = 0;
  6. struct DNode *p = pdlist->next;
  7. for(; p!=NULL; p=p->next)
  8. {
  9. count++;
  10. }
  11. return count;
  12. }

之后依旧是和之前一样的双销毁

  1. void Clear(struct DNode *pdlist)
  2. {
  3. Destroy1(pdlist);
  4. }
  5. //销毁1 无限头删
  6. void Destroy1(struct DNode *pdlist)
  7. {
  8. //assert
  9. while(!IsEmpty(pdlist))
  10. {
  11. Del_head(pdlist);
  12. }
  13. }
  14. //销毁2 不借助头结点,有两个辅助指针
  15. void Destroy2(struct DNode *pdlist)
  16. {
  17. assert(pdlist != NULL);
  18. struct DNode *p = pdlist->next;
  19. struct DNode *q = NULL;
  20. pdlist->next = NULL;
  21. while(p != NULL)
  22. {
  23. q = p->next;
  24. free(p);
  25. p = q;
  26. }
  27. }

完整代码

  1. #include <stdio.h>
  2. #include <assert.h>
  3. #include <stdlib.h>
  4. #include "dlist.h"
  5. 可实现的操作:
  6. //初始化
  7. void Init_dlist(struct DNode * pdlist)
  8. {
  9. assert(pdlist != NULL);
  10. //pdlist->data 头结点的数据域不使用
  11. pdlist->next = NULL;
  12. pdlist->prior = NULL;
  13. }
  14. //头插
  15. bool Insert_head(struct DNode *pdlist, ELEM_TYPE val)
  16. {
  17. //0.安全性处理
  18. assert(pdlist != NULL);
  19. //1.购买新节点
  20. struct DNode *pnewnode = (struct DNode *)malloc(1 * sizeof(struct DNode));
  21. assert(pnewnode != NULL);
  22. pnewnode->data = val;
  23. //2.找到合适的插入位置(其实就是找到插入在哪一个节点后边,用指针p指向)
  24. //因为是头插,所以直接使用pdlist即可
  25. //3.插入 我们的规则是:1,2,4,3
  26. // 先处理自身的两个指针域(1,2)
  27. // 再处理插入位置的下一个节点的prior域(4),但是4有特例(空链表进行头插),不存在
  28. // 最后处理插入位置的上一个节点的next域(3)
  29. pnewnode->next = pdlist->next;//1
  30. pnewnode->prior = pdlist;//2
  31. if(pdlist->next != NULL)//说明不是空链表,则不是特例,4存在
  32. {
  33. //此时,插入位置的下一个节点可以通过pdlist->next访问到,还可以通过pnewnode->next访问到
  34. pdlist->next->prior = pnewnode;//4
  35. //pnewnode->next->prior = pnewnode;//4
  36. }
  37. pdlist->next = pnewnode;
  38. return NULL;
  39. }
  40. //尾插
  41. bool Insert_tail(struct DNode *pdlist, ELEM_TYPE val)
  42. {
  43. //0
  44. assert(pdlist != NULL);
  45. //1.购买新节点
  46. struct DNode *pnewnode = (struct DNode *)malloc(1 * sizeof(struct DNode));
  47. assert(pnewnode != NULL);
  48. pnewnode->data = val;
  49. //2.找到合适的插入位置,用指针p指向插入位置的前一个节点
  50. //判断是否使用带前驱的for循环
  51. struct DNode *p = pdlist;
  52. for(; p->next!=NULL; p=p->next);
  53. //3.插入(不存在特殊情况,每一种情况都需要修改三个指针域)
  54. //按照之前编号顺序,修改的这三个指针域分别是1,2,(4不存在),3
  55. pnewnode->next = p->next;//pnewnode->next = NULL;//1
  56. pnewnode->prior = p;//2
  57. //4不存在
  58. p->next = pnewnode;//3
  59. return true;
  60. }
  61. //按位置插
  62. bool Insert_pos(struct DNode *pdlist, int pos, ELEM_TYPE val)
  63. {
  64. //因为,在写这个函数之前,头插和尾插已经实现,所以这里可以直接调用
  65. //0.安全性处理
  66. assert(pdlist != NULL);
  67. assert(pos>=0 && pos<=Get_length(pdlist));
  68. //1.分类处理,将头插和尾插的情况,分别调用对应的函数处理
  69. if(pos == 0)//头插
  70. {
  71. return Insert_head(pdlist, val);
  72. }
  73. if(pos == Get_length(pdlist))//尾插
  74. {
  75. return Insert_tail(pdlist, val);
  76. }
  77. //如果既不是头插,也不是尾插,则只有可能是中间插入,1,2,4,3,都存在
  78. //2.剩下来的都是中间位置插入,都正常情况,修改4个指针域
  79. //2.1 购买新节点
  80. struct DNode *pnewnode = (struct DNode *)malloc(1 * sizeof(struct DNode));
  81. assert(pnewnode != NULL);
  82. pnewnode->data = val;
  83. //2.2 找到合适的插入位置,指针p(pos=="几",指针p从头结点出发向后走pos步)
  84. struct DNode *p = pdlist;
  85. for(int i=0; i<pos; i++)
  86. {
  87. p = p->next;
  88. }
  89. //2,3 正常插入,1,2,4,3 都存在,4不需要去判断
  90. pnewnode->next = p->next;//1
  91. pnewnode->prior = p;//2
  92. p->next->prior = pnewnode;//4 pnewnode->next->prior = pnownode;
  93. p->next = pnewnode;//3
  94. return true;
  95. }
  96. //头删 //这里写的也要注意,也存在特例
  97. bool Del_head(struct DNode *pdlist)
  98. {
  99. //0.安全性处理
  100. assert(pdlist != NULL);
  101. if(IsEmpty(pdlist))
  102. {
  103. return false;
  104. }
  105. //1.用指针p指向待删除节点
  106. struct DNode *p = pdlist->next;
  107. //2.用指针q指向待删除节点的上一个节点
  108. //因为是头删,所以这里指针q用指针pdlist代替
  109. //3.跨越指向(存在特例,正常情况下需要修改两个指针域,而特例时,只需要修改一个指针域)
  110. pdlist->next = p->next;
  111. if(p->next != NULL)//先得判断待删除节点的下一个节点是否存在
  112. {
  113. p->next->prior = pdlist;
  114. }
  115. //4.释放
  116. free(p);
  117. return true;
  118. }
  119. //尾删
  120. bool Del_tail(struct DNode *pdlist)
  121. {
  122. //0.
  123. assert(pdlist != NULL);
  124. if(IsEmpty(pdlist))
  125. {
  126. return false;
  127. }
  128. //1.用指针p指向待删除节点
  129. struct DNode *p = pdlist;
  130. for(; p->next!=NULL; p=p->next);
  131. //2.用指针q指向待删除节点的上一个节点
  132. struct DNode *q = pdlist;
  133. for(; q->next!=p; q=q->next);
  134. //3.跨越指向(不存在特例,永远只需要去修改待删除节点的前一个节点的next域)
  135. q->next = p->next;//q->next = NULL;
  136. //4.释放
  137. free(p);
  138. return true;
  139. }
  140. //按位置删
  141. bool Del_pos(struct DNode *pdlist, int pos)
  142. {
  143. //0.安全性处理
  144. assert(pdlist != NULL);
  145. assert(pos >=0 && pos<Get_length(pdlist));
  146. if(IsEmpty(pdlist))
  147. {
  148. return false;
  149. }
  150. //1.分类处理,将头删和尾删的情况,分别调用对应的函数处理
  151. if(pos == 0)//头删
  152. {
  153. return Del_head(pdlist);
  154. }
  155. if(pos == Get_length(pdlist)-1)//尾删
  156. {
  157. return Del_tail(pdlist);
  158. }
  159. //如果既不是头删,也不是尾删,则只有可能是中间删除,则统一需要修改两个指针域
  160. //2.剩下来的都是中间位置删除,统一需要修改两个指针域
  161. //2.1 找到q,让q从头结点开始向后走pos步
  162. struct DNode *q = pdlist;
  163. for(int i=0; i<pos; i++)
  164. {
  165. q = q->next;
  166. }
  167. //2.2 找到p,p=q->next
  168. struct DNode *p = q->next;
  169. //2.3 跨越指向+释放
  170. q->next = p->next;
  171. p->next->prior = q;
  172. free(p);
  173. return true;
  174. }
  175. //按值删
  176. bool Del_val(struct DNode *pdlist, ELEM_TYPE val)
  177. {
  178. //0.安全性处理
  179. //1.用指针p指向待删除节点,用search函数
  180. struct DNode *p = Search(pdlist, val);
  181. if(p == NULL)
  182. {
  183. return false;
  184. }
  185. //此时,代码执行到这里,可以保证待删除节点存在,且现在用指针p指向
  186. //2.用指针q指向待删除节点的上一个节点
  187. struct DNode *q = pdlist;
  188. for( ; q->next!=p; q=q->next);
  189. //3.跨越指向(有可能存在特例,例如如果待删除节点是尾结点,则只需要处理一个指针域,反之都是两个)
  190. if(p->next == NULL)//判断待删除节点是否是尾结点
  191. {
  192. q->next = NULL;//q->next = p->next;
  193. }
  194. else//如果待删除节点不是尾结点
  195. {
  196. q->next = p->next;
  197. p->next->prior = q;
  198. }
  199. //4.释放
  200. free(p);
  201. return true;
  202. }
  203. //查找 //查找到,返回的是查找到的这个节点的地址
  204. struct DNode *Search(struct DNode *pdlist, ELEM_TYPE val)
  205. {
  206. //0.安全性处理
  207. //1.判断使用哪种for循环
  208. //使用不需要前驱的for循环
  209. struct DNode *p = pdlist->next;
  210. for(; p!=NULL; p=p->next)
  211. {
  212. if(p->data == val)
  213. {
  214. return p;
  215. }
  216. }
  217. return NULL;
  218. }
  219. //获取有效值个数
  220. int Get_length(struct DNode *pdlist)
  221. {
  222. //assert
  223. //使用不需要前驱的for循环
  224. int count = 0;
  225. struct DNode *p = pdlist->next;
  226. for(; p!=NULL; p=p->next)
  227. {
  228. count++;
  229. }
  230. return count;
  231. }
  232. //判空
  233. bool IsEmpty(struct DNode *pdlist)
  234. {
  235. return pdlist->next == NULL;
  236. }
  237. //清空
  238. void Clear(struct DNode *pdlist)
  239. {
  240. Destroy1(pdlist);
  241. }
  242. //销毁1 无限头删
  243. void Destroy1(struct DNode *pdlist)
  244. {
  245. //assert
  246. while(!IsEmpty(pdlist))
  247. {
  248. Del_head(pdlist);
  249. }
  250. }
  251. //销毁2 不借助头结点,有两个辅助指针
  252. void Destroy2(struct DNode *pdlist)
  253. {
  254. assert(pdlist != NULL);
  255. struct DNode *p = pdlist->next;
  256. struct DNode *q = NULL;
  257. pdlist->next = NULL;
  258. while(p != NULL)
  259. {
  260. q = p->next;
  261. free(p);
  262. p = q;
  263. }
  264. }
  265. //打印
  266. void Show(struct DNode *pdlist)
  267. {
  268. //assert
  269. //使用不需要前驱的for循环
  270. struct DNode *p = pdlist->next;
  271. for(; p!=NULL; p=p->next)
  272. {
  273. printf("%d ", p->data);
  274. }
  275. printf("\n");
  276. }

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

闽ICP备14008679号