• 数据结构 — 双向链表


    目录

    1、双向链表

    2.1、双向不循环链表

    2.2、双向循环链表


    1、双向链表

            双向链表和单向链表相比,最大的不同是指针域中多了一个指向前一个节点的地址域。这样一来,双向链表中的每个节点就保存了前后节点的地址,从而通过地址形成一条链式的存储结构。

            双向链表的最大便利之处在于查询链表时不仅可以正向查找也可以反向查找,甚至如果当前查询的位置在链表中间的位置的时候,可以反方向两头查找,提高查找的速度和效率,增加了便利。

            双向链表也有循环和不循环两种方式,下面分别介绍。

    2.1、双向不循环链表

            双向不循环链表是头结点的前一个指针指向头结点自身,尾节点的后一个指针指向为NULL。示意图如下:

    双向不循环链表的代码实现如下示例:

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. /*** 定义链表操作的位置:开头、中间、结尾 ***/
    4. typedef enum
    5. {
    6. Head = 1,
    7. Middle,
    8. End,
    9. }Location;
    10. /*** 定义一个链表节点 ******/
    11. // 1.定义一个链表节点的数据域。以记录一个苹果的信息为例,为方便说明,假设每个苹果的信息各不相同
    12. typedef struct LinkData_struct
    13. {
    14. int weight; // 苹果的重量
    15. int hight; // 苹果的高度
    16. // ... // 还可以定义更多的其他的数据
    17. }LinkData_t;
    18. typedef struct LinkNode_Struct
    19. {
    20. LinkData_t data; // 链表节点的数据域
    21. struct LinkNode_Struct *Prev; // 链表节点的上一个节点的指针域
    22. struct LinkNode_Struct *Next; // 链表节点的下一个节点的指针域
    23. }LinkNode_t;
    24. /*** 定义一个单向链表的头结点 ***/
    25. LinkNode_t AppleInfo_head; //作为单向链表的一个头结点
    26. // 初始化单向链表的头结点
    27. void Init_LinkNode(LinkNode_t * HeadNode)
    28. {
    29. HeadNode->Next = NULL;
    30. HeadNode->Prev = NULL;
    31. }
    32. // 创建一个链表节点并接入到链表中
    33. LinkNode_t * LinkNode_Create(LinkNode_t * HeadNode, LinkData_t *InsetData)
    34. {
    35. LinkNode_t *Prev_pf,*Next_pf;
    36. LinkNode_t *xReturn = NULL;
    37. LinkNode_t *Node = (LinkNode_t *)malloc(sizeof(LinkNode_t));
    38. if (Node == NULL)
    39. {
    40. printf("节点创建失败!!!\r\n");
    41. return NULL;
    42. }
    43. Prev_pf = Next_pf = HeadNode;
    44. // 第一节点从头结点后面开始插入
    45. if (HeadNode->Next == NULL && HeadNode->Prev == NULL)
    46. {
    47. HeadNode->Next = Node;
    48. Node->Next = NULL;
    49. Node->Prev = HeadNode;
    50. Node->data.hight = InsetData->hight;
    51. Node->data.weight = InsetData->weight;
    52. xReturn = Node;
    53. }
    54. else
    55. {
    56. while (Next_pf->Next != NULL)
    57. {
    58. Next_pf = Next_pf->Next;
    59. }
    60. Next_pf->Next = Node;
    61. Node->Prev = Next_pf;
    62. Node->Next = NULL;
    63. Node->data.hight = InsetData->hight;
    64. Node->data.weight = InsetData->weight;
    65. xReturn = Node;
    66. }
    67. printf("完成一个链表节点的创建,地址 = 0x%X\r\n",xReturn);
    68. return xReturn;
    69. }
    70. // 删除链表中的某个节点,根据weight进行删除
    71. void destroy_LinkNode(LinkNode_t * HeadNode, int weight)
    72. {
    73. int deleteFlag = 0;
    74. LinkNode_t *pf;
    75. pf = HeadNode;
    76. do
    77. {
    78. pf = pf->Next;
    79. if (pf->data.weight == weight)
    80. {
    81. deleteFlag = 1;
    82. break;
    83. }
    84. }while (pf->Next != NULL);
    85. if (!deleteFlag)
    86. {
    87. printf("链表中找不到这个节点!!!\r\n");
    88. }
    89. else
    90. {
    91. if (pf->Next == NULL)
    92. {
    93. pf->Prev->Next = NULL;
    94. }
    95. else
    96. {
    97. pf->Prev->Next = pf->Next;
    98. pf->Next->Prev = pf->Prev;
    99. }
    100. free(pf);
    101. printf("节点weight = %d 销毁成功!\r\n",weight);
    102. }
    103. }
    104. // 输出显示整条链表
    105. void print_LinkNode_Info(LinkNode_t * HeadNode)
    106. {
    107. int length = 0;
    108. LinkNode_t *pf = HeadNode;
    109. if (pf->Next == NULL)
    110. {
    111. printf("该链表长度为零,不能输出结果!\r\n");
    112. return;
    113. }
    114. do
    115. {
    116. pf = pf->Next;
    117. printf("weight = %d\r\n", (int)pf->data.weight);
    118. printf("height = %d\r\n", (int)pf->data.hight);
    119. printf("\r\n");
    120. length++;
    121. }while (pf->Next != NULL);
    122. printf("链表输出完成。长度为:%d\r\n",length);
    123. }
    124. // 按条件查询链表中某个节点的数据
    125. void search_LinkNode_Info(LinkNode_t * HeadNode, int weight)
    126. {
    127. int length = 0;
    128. char search_flag = 0;
    129. LinkNode_t *pf = HeadNode;
    130. if (pf->Next == NULL)
    131. {
    132. printf("该链表长度为零,不能输出结果!\r\n");
    133. return;
    134. }
    135. do
    136. {
    137. pf = pf->Next;
    138. if(pf->data.weight == weight)
    139. {
    140. search_flag = 1;
    141. printf("weight = %d\r\n", (int)pf->data.weight);
    142. printf("height = %d\r\n", (int)pf->data.hight);
    143. printf("\r\n");
    144. }
    145. length++;
    146. }while (pf->Next != NULL);
    147. if(search_flag)
    148. {
    149. printf("链表查找结束。所在节点位置为:%d\r\n",length);
    150. }
    151. else
    152. {
    153. printf("整个链表中无满足此条件的节点!\r\n");
    154. }
    155. }
    156. // 查询链表中有几个相同的数据
    157. void search_Same_LinkNode_Info(LinkNode_t * HeadNode, int weight)
    158. {
    159. int length = 0,cnt = 0;
    160. char search_flag = 0;
    161. LinkNode_t *pf = HeadNode;
    162. if (pf->Next == NULL)
    163. {
    164. printf("该链表长度为零,不能输出结果!\r\n");
    165. return;
    166. }
    167. do
    168. {
    169. pf = pf->Next;
    170. length++;
    171. if(pf->data.weight == weight)
    172. {
    173. search_flag = 1;
    174. printf("weight = %d\r\n", (int)pf->data.weight);
    175. printf("height = %d\r\n", (int)pf->data.hight);
    176. printf("这个节点在链表中的位置:%d\r\n",length);
    177. cnt++;
    178. }
    179. }while (pf->Next != NULL);
    180. if(search_flag)
    181. {
    182. printf("链表查找结束。相同数据的节点数量为:%d\r\n",cnt);
    183. }
    184. else
    185. {
    186. printf("整个链表中无满足此条件的节点!\r\n");
    187. }
    188. }
    189. void print_debug_info(void)
    190. {
    191. printf("******** 链表控制台 *****************\r\n");
    192. printf("* \r\n");
    193. printf("* 1:创建链表 \r\n");
    194. printf("* 2:删除链表 \r\n");
    195. printf("* 3:显示整条链表的数据 \r\n");
    196. printf("* 4:按条件查找链表节点 \r\n");
    197. printf("* 5:查找链表中有几个相同数据的节点 \r\n");
    198. printf("* 8:清空显示 \r\n");
    199. printf("* 9:结束运行 \r\n");
    200. printf("* \r\n");
    201. printf("************************************\r\n");
    202. }
    203. int main(int argc, char *argv[])
    204. {
    205. int Num,i,j;
    206. int Options;
    207. int weight,height;
    208. LinkData_t InsetData;
    209. Init_LinkNode(&AppleInfo_head);
    210. while (1)
    211. {
    212. print_debug_info();
    213. printf("\r\n请输入需要操作的键值,按回车键确认!\r\n");
    214. scanf("%d", &Options);
    215. switch (Options)
    216. {
    217. case 1:
    218. /*** 创建任意长度的链表 **********************************************************/
    219. printf("请输入需要创建的链表节点的数量,按回车结束!\r\n");
    220. scanf("%d", &Num);
    221. printf("请输入节点的数据:\r\n");
    222. for (i = 0; i < Num; i++)
    223. {
    224. printf("请输入第 %d 节点的数据,weight = Height = ,输入完毕按回车结束!\r\n",i+1);
    225. scanf("%d %d", &weight,&height);
    226. InsetData.weight = weight;
    227. InsetData.hight = height;
    228. printf("weight = %d height = %d\r\n",weight,height);
    229. if(LinkNode_Create(&AppleInfo_head, &InsetData) > NULL)
    230. {
    231. printf("节点 %d 创建成功!\r\n\r\n\r\n",i+1);
    232. }
    233. else
    234. {
    235. printf("节点 %d 创建失败!\r\n\r\n\r\n",i+1);
    236. }
    237. }
    238. printf("\r\n");
    239. break;
    240. case 2:
    241. /******* 删除链表中的某个节点 ***************************************************/
    242. printf("请输入需要删除的链表节点的weight,按回车结束!\r\n");
    243. scanf("%d", &Num);
    244. destroy_LinkNode(&AppleInfo_head, Num);
    245. printf("\r\n");
    246. break;
    247. case 3:
    248. printf("链表数据为:\r\n");
    249. print_LinkNode_Info(&AppleInfo_head);
    250. printf("\r\n");
    251. break;
    252. case 4:
    253. printf("请输入需要查找的链表节点的weight,按回车结束!\r\n");
    254. scanf("%d", &Num);
    255. search_LinkNode_Info(&AppleInfo_head, Num);
    256. printf("\r\n");
    257. break;
    258. case 5:
    259. printf("请输入需要查找的链表节点的weight,按回车结束!\r\n");
    260. scanf("%d", &Num);
    261. search_Same_LinkNode_Info(&AppleInfo_head, Num);
    262. printf("\r\n");
    263. break;
    264. case 8:
    265. system("cls");
    266. break;
    267. case 9:
    268. printf("退出链表控制台,请再次运行程序!\r\n");
    269. return;
    270. break;
    271. default:
    272. printf("无效的键值,请重新输入!\r\n");
    273. break;
    274. }
    275. }
    276. return 0;
    277. }
     
       

    2.2、双向循环链表

            双向循环链表是头结点的前一个指针指向尾结点的地址,尾节点的后一个指针指向头结点的地址所在,从而构成一种链式的循环结构。示意图如下:

    双向循环链表的示意代码如下:

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. /*** 定义链表操作的位置:开头、中间、结尾 ***/
    4. typedef enum
    5. {
    6. Head = 1,
    7. Middle,
    8. End,
    9. }Location;
    10. /*** 定义一个链表节点 ******/
    11. // 1.定义一个链表节点的数据域。以记录一个苹果的信息为例,为方便说明,假设每个苹果的信息各不相同
    12. typedef struct LinkData_struct
    13. {
    14. int weight; // 苹果的重量
    15. int hight; // 苹果的高度
    16. // ... // 还可以定义更多的其他的数据
    17. }LinkData_t;
    18. typedef struct LinkNode_Struct
    19. {
    20. LinkData_t data; // 链表节点的数据域
    21. struct LinkNode_Struct *Prev; // 链表节点的上一个节点的指针域
    22. struct LinkNode_Struct *Next; // 链表节点的下一个节点的指针域
    23. }LinkNode_t;
    24. /*** 定义一个单向链表的头结点 ***/
    25. LinkNode_t AppleInfo_head; //作为单向链表的一个头结点
    26. // 初始化单向链表的头结点
    27. void Init_LinkNode(LinkNode_t * HeadNode)
    28. {
    29. HeadNode->Next = NULL;
    30. HeadNode->Prev = HeadNode; // 双向循环链表的头结点的前一个指针指向头结点本身
    31. }
    32. // 创建一个链表节点并接入到链表中
    33. LinkNode_t * LinkNode_Create(LinkNode_t * HeadNode, LinkData_t *InsetData)
    34. {
    35. LinkNode_t *Prev_pf,*Next_pf;
    36. LinkNode_t *xReturn = NULL;
    37. LinkNode_t *Node = (LinkNode_t *)malloc(sizeof(LinkNode_t));
    38. if (Node == NULL)
    39. {
    40. printf("节点创建失败!!!\r\n");
    41. return NULL;
    42. }
    43. Prev_pf = Next_pf = HeadNode;
    44. // 第一节点从头结点后面开始插入
    45. if (HeadNode->Next == NULL && HeadNode->Prev == HeadNode)
    46. {
    47. HeadNode->Next = Node;
    48. HeadNode->Prev = Node;
    49. Node->Next = HeadNode;
    50. Node->Prev = HeadNode;
    51. Node->data.hight = InsetData->hight;
    52. Node->data.weight = InsetData->weight;
    53. xReturn = Node;
    54. }
    55. else
    56. {
    57. while (Next_pf->Next != HeadNode)
    58. {
    59. Next_pf = Next_pf->Next;
    60. }
    61. Next_pf->Next = Node;
    62. HeadNode->Prev = Node;
    63. Node->Prev = Next_pf;
    64. Node->Next = HeadNode;
    65. Node->data.hight = InsetData->hight;
    66. Node->data.weight = InsetData->weight;
    67. xReturn = Node;
    68. }
    69. printf("完成一个链表节点的创建,地址 = 0x%X\r\n",xReturn);
    70. return xReturn;
    71. }
    72. // 删除链表中的某个节点,根据weight进行删除
    73. void destroy_LinkNode(LinkNode_t * HeadNode, int weight)
    74. {
    75. int deleteFlag = 0;
    76. LinkNode_t *pf;
    77. pf = HeadNode;
    78. do
    79. {
    80. pf = pf->Next;
    81. if (pf->data.weight == weight)
    82. {
    83. deleteFlag = 1;
    84. break;
    85. }
    86. }while (pf->Next != HeadNode);
    87. if (!deleteFlag)
    88. {
    89. printf("链表中找不到这个节点!!!\r\n");
    90. }
    91. else
    92. {
    93. if (pf->Next == HeadNode)
    94. {
    95. pf->Prev->Next = HeadNode;
    96. HeadNode->Prev = pf;
    97. }
    98. else
    99. {
    100. pf->Prev->Next = pf->Next;
    101. pf->Next->Prev = pf->Prev;
    102. }
    103. free(pf);
    104. printf("节点weight = %d 销毁成功!\r\n",weight);
    105. }
    106. }
    107. // 输出显示整条链表
    108. void print_LinkNode_Info(LinkNode_t * HeadNode)
    109. {
    110. int length = 0;
    111. LinkNode_t *pf = HeadNode;
    112. if (pf->Next == NULL)
    113. {
    114. printf("该链表长度为零,不能输出结果!\r\n");
    115. return;
    116. }
    117. do
    118. {
    119. pf = pf->Next;
    120. printf("weight = %d\r\n", (int)pf->data.weight);
    121. printf("height = %d\r\n", (int)pf->data.hight);
    122. printf("\r\n");
    123. length++;
    124. }while (pf->Next != HeadNode);
    125. printf("链表输出完成。长度为:%d\r\n",length);
    126. }
    127. // 按条件查询链表中某个节点的数据
    128. void search_LinkNode_Info(LinkNode_t * HeadNode, int weight)
    129. {
    130. int length = 0;
    131. char search_flag = 0;
    132. LinkNode_t *pf = HeadNode;
    133. if (pf->Next == NULL)
    134. {
    135. printf("该链表长度为零,不能输出结果!\r\n");
    136. return;
    137. }
    138. do
    139. {
    140. pf = pf->Next;
    141. if(pf->data.weight == weight)
    142. {
    143. search_flag = 1;
    144. printf("weight = %d\r\n", (int)pf->data.weight);
    145. printf("height = %d\r\n", (int)pf->data.hight);
    146. printf("\r\n");
    147. }
    148. length++;
    149. }while (pf->Next != HeadNode);
    150. if(search_flag)
    151. {
    152. printf("链表查找结束。所在节点位置为:%d\r\n",length);
    153. }
    154. else
    155. {
    156. printf("整个链表中无满足此条件的节点!\r\n");
    157. }
    158. }
    159. // 查询链表中有几个相同的数据
    160. void search_Same_LinkNode_Info(LinkNode_t * HeadNode, int weight)
    161. {
    162. int length = 0,cnt = 0;
    163. char search_flag = 0;
    164. LinkNode_t *pf = HeadNode;
    165. if (pf->Next == NULL)
    166. {
    167. printf("该链表长度为零,不能输出结果!\r\n");
    168. return;
    169. }
    170. do
    171. {
    172. pf = pf->Next;
    173. length++;
    174. if(pf->data.weight == weight)
    175. {
    176. search_flag = 1;
    177. printf("weight = %d\r\n", (int)pf->data.weight);
    178. printf("height = %d\r\n", (int)pf->data.hight);
    179. printf("这个节点在链表中的位置:%d\r\n",length);
    180. cnt++;
    181. }
    182. }while (pf->Next != HeadNode);
    183. if(search_flag)
    184. {
    185. printf("链表查找结束。相同数据的节点数量为:%d\r\n",cnt);
    186. }
    187. else
    188. {
    189. printf("整个链表中无满足此条件的节点!\r\n");
    190. }
    191. }
    192. void print_debug_info(void)
    193. {
    194. printf("******** 链表控制台 *****************\r\n");
    195. printf("* \r\n");
    196. printf("* 1:创建链表 \r\n");
    197. printf("* 2:删除链表 \r\n");
    198. printf("* 3:显示整条链表的数据 \r\n");
    199. printf("* 4:按条件查找链表节点 \r\n");
    200. printf("* 5:查找链表中有几个相同数据的节点 \r\n");
    201. printf("* 8:清空显示 \r\n");
    202. printf("* 9:结束运行 \r\n");
    203. printf("* \r\n");
    204. printf("************************************\r\n");
    205. }
    206. int main(int argc, char *argv[])
    207. {
    208. int Num,i,j;
    209. int Options;
    210. int weight,height;
    211. LinkData_t InsetData;
    212. Init_LinkNode(&AppleInfo_head);
    213. while (1)
    214. {
    215. print_debug_info();
    216. printf("\r\n请输入需要操作的键值,按回车键确认!\r\n");
    217. scanf("%d", &Options);
    218. switch (Options)
    219. {
    220. case 1:
    221. /*** 创建任意长度的链表 **********************************************************/
    222. printf("请输入需要创建的链表节点的数量,按回车结束!\r\n");
    223. scanf("%d", &Num);
    224. printf("请输入节点的数据:\r\n");
    225. for (i = 0; i < Num; i++)
    226. {
    227. printf("请输入第 %d 节点的数据,weight = Height = ,输入完毕按回车结束!\r\n",i+1);
    228. scanf("%d %d", &weight,&height);
    229. InsetData.weight = weight;
    230. InsetData.hight = height;
    231. printf("weight = %d height = %d\r\n",weight,height);
    232. if(LinkNode_Create(&AppleInfo_head, &InsetData) > NULL)
    233. {
    234. printf("节点 %d 创建成功!\r\n\r\n\r\n",i+1);
    235. }
    236. else
    237. {
    238. printf("节点 %d 创建失败!\r\n\r\n\r\n",i+1);
    239. }
    240. }
    241. printf("\r\n");
    242. break;
    243. case 2:
    244. /******* 删除链表中的某个节点 ***************************************************/
    245. printf("请输入需要删除的链表节点的weight,按回车结束!\r\n");
    246. scanf("%d", &Num);
    247. destroy_LinkNode(&AppleInfo_head, Num);
    248. printf("\r\n");
    249. break;
    250. case 3:
    251. printf("链表数据为:\r\n");
    252. print_LinkNode_Info(&AppleInfo_head);
    253. printf("\r\n");
    254. break;
    255. case 4:
    256. printf("请输入需要查找的链表节点的weight,按回车结束!\r\n");
    257. scanf("%d", &Num);
    258. search_LinkNode_Info(&AppleInfo_head, Num);
    259. printf("\r\n");
    260. break;
    261. case 5:
    262. printf("请输入需要查找的链表节点的weight,按回车结束!\r\n");
    263. scanf("%d", &Num);
    264. search_Same_LinkNode_Info(&AppleInfo_head, Num);
    265. printf("\r\n");
    266. break;
    267. case 8:
    268. system("cls");
    269. break;
    270. case 9:
    271. printf("退出链表控制台,请再次运行程序!\r\n");
    272. return;
    273. break;
    274. default:
    275. printf("无效的键值,请重新输入!\r\n");
    276. break;
    277. }
    278. }
    279. return 0;
    280. }

    总结:

            单向链表和双向链表都有各自的用处,到底用哪一个好还是要根据自己到底需要什么样的功能决定,实际问题实际处理。实际开发中,只要不是一些大型的项目,或者一些要求高效率快速的要求,一般单向的链表也就足够了。

  • 相关阅读:
    Windows 安装的虚拟环境位置在哪里,怎么找到pycharm对应的python解释器
    人工智能AI 全栈体系(四)
    家政预约小程序07服务分类展示
    【微信小程序】事件传参的两种方式
    第02章 变量
    Vim操作的常用命令记录
    知识图谱丨知识图谱赋能企业数字化转型
    掌握Linux常用命令,扫平面试需求障碍
    xilinx primitives(原语)
    东北大学acm暑期夏令营结构体
  • 原文地址:https://blog.csdn.net/weixin_43866583/article/details/127090511