读者可以先阅读这一篇:数据结构——单链表的增加、删除、查找、修改,详细解析_昵称就是昵称吧的博客-CSDN博客,可以更好的理解带头双向循环链表


目录

一、带头双向循环链表的处理和介绍

1、带头双向循环链表的概念

2、带头双向循环链表的结构

3、节点的处理

4、头节点的处理

5、节点(结构体)内存空间的开辟

二、链表的主体框架

1、整体框架

2、主菜单

三、功能的实现

1、创建一个新节点的函数

2、初始化创建头节点的函数

3、打印节点数据的函数

4、尾插和头插增加节点的函数

4.1 从尾部插入节点的函数

4.2 从头部插入节点的函数

5、尾删和头删减少节点的函数

5.1 从尾部删除节点的函数

5.2 从头部删除节点的函数

6、提供一个数据,找到这个数据所在的节点的函数

7、给一个数据,在其所在节点前面插入一个节点的函数

8、给一个数据,删除这个数据所在的节点的函数

9、修改数据的函数

10、部分功能的简化与升级

10.1 尾插和头插函数的简化升级

四、总代码

五、代码运行实例


一、带头双向循环链表的处理和介绍

1、带头双向循环链表的概念

链表的概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

带头双向循环链表是链表中最复杂的,因为它具有了链表存在的结构中所有的结构:带头,双向和循环。

1、带头和不带头:带头就是链表带有头节点,这个头节点只具有哨兵位的作用,意思就是它不随着链表的改变而改变,定义之后就固定不变了,并且头节点里面不存放数据;不带头就是链表没有头节点。

2、双向和单项:双向就是一个节点,既存上一个节点的地址,也存下一个节点的地址;单向就是一个节点,只存下一个节点的地址。

3、循环和非循环:循环就是这个链表的最后一个节点里的指针存放的地址,如果链表带头,就存放的头节点的地址;如果链表不带头,就存放第一个节点的地址;非循环就是这个链表的最后一个节点存放的地址是空的。


2、带头双向循环链表的结构

头节点里面是不存放数据的,只具有哨兵位的作用,各个节点之间通过节点里面的指针进行链接。单向链表里面,每个节点里只有一个指针,因为只需要指向下一个节点;但是双向链表中,每个节点里有两个指针,一个指向上一个节点,一个指向下一个节点。


3、节点的处理

因为双向链表里面每个节点,都会存储一个数据和两个指针prev和next,指针prev指向上一个节点,指针next指向下一个节点,所以用结构体来表示节点。

typedef int ListDataType;//类型名重定义typedef struct ListNode
{struct ListNode* prev;struct ListNode* next;ListDataType data;
}ListNode;

4、头节点的处理

在这里,我们用指针phead/plist表示头节点的地址,因为是带有头双向循环链表,所以定义头节点的时候,其节点里的指针prev和next都必须指向自己。

   phead->prev = phead;//头节点不存放数据phead->next = phead;

5、节点(结构体)内存空间的开辟

因为每个节点都是结构体,为了不会导致内存空间的浪费,需要用一个节点,就开辟一个节点。所以用动态内存开辟函数malloc即可,每次开辟一个结构体大小的内存空间。但是要注意,每个节点创建的时候,其里面的指针prev和next都要先置为空指针,方便后面的修改。

   ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));newnode->next = NULL;//因为是新节点,都先初始化置为空指针newnode->prev = NULL;newnode->data = x;//将想要的数据放入进去

二、链表的主体框架

1、整体框架

int main()
{int input = 0;do{meun();printf("请输入想要进行的操作:");scanf("%d", &input);switch (input){case 1:{printf("从尾部插入节点:\n");testList1();//验证尾插函数printf("\n");break;}case 2:{printf("从头部插入节点:\n");testList2();//验证头插函数printf("\n");break;}case 3:{printf("从尾部删除节点:\n");testList3();//验证尾删函数printf("\n");break;}case 4:{printf("从头部删除节点:\n");testList4();//验证头删函数printf("\n");break;}case 5:{printf("给数据找其节点:\n");testList5();//验证查找节点printf("\n");break;}case 6:{printf("修改数据:\n");testList6();//验证修改数据printf("\n");break;}case 7:{printf("插入节点:\n");testList7();//验证插入节点printf("\n");break;}case 8:{printf("删除节点:\n");testList8();//验证删除节点printf("\n");break;}case 0:{printf("退出操作\n");break;}default:{printf("选择错误,请重新选择操作\n");break;}}} while (input);return 0;
}

do......while循环语句swith分支语句来进入相应的功能。


2、主菜单

//主菜单
void meun()
{printf("*****************************************************\n");printf("*           1、尾插               2、头插           *\n");printf("*           3、尾删               4、头删           *\n");printf("*           5、查找               6、修改           *\n");printf("*           7、任意数据前插入     8、删除任意数据   *\n");printf("*           0、退出                                 *\n");printf("*****************************************************\n");
}

三、功能的实现

1、创建一个新节点的函数

//创建一个新节点的函数
ListNode* BuyListNode(ListDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));newnode->next = NULL;//因为是新节点,都先初始化置为空指针newnode->prev = NULL;newnode->data = x;//将想要的数据放入进去return newnode;
}

解释请看代码里面的注解,非常详细。


2、初始化创建头节点的函数

//创建一个新节点的函数
ListNode* BuyListNode(ListDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));newnode->next = NULL;//因为是新节点,都先初始化置为空指针newnode->prev = NULL;newnode->data = x;//将想要的数据放入进去return newnode;
}//初始化创建头节点
ListNode* ListInit()
{ListNode* phead = BuyListNode(0);//这里给的数据0是无用的,只是方便使用创建新节点函数phead->prev = phead;//头节点里date不把数据0放入(因为无用),并且其内的指针prev和next都指向自己phead->next = phead;return phead;
}

解释请看代码里面的注解,非常详细。


3、打印节点数据的函数

//打印节点的数据
void ListPrint(ListNode* phead)
{assert(phead);//断言函数:防止忘记初始化创建头节点ListNode* cur = phead->next;printf("Phead <-> ");while (cur != phead)//遍历一遍:循环最后一个节点里的指针next,next存储的是头节点phead的地址{printf("%d <-> ", cur->data);cur = cur->next;}printf("phead\n");
}

解释请看代码里面的注解,非常详细。


4、尾插和头插增加节点的函数

4.1 从尾部插入节点的函数

//尾插函数
void ListPushBack(ListNode* phead,ListDataType x)
{assert(phead);//断言函数:防止忘记初始化创建头节点ListNode* tail = phead->prev;//尾插前,通过头节点找到最后一个节点tailListNode* newnode = BuyListNode(x);//创建需要插入的新节点tail->next = newnode;//使节点phead、tail、newnode里的prev或next指向对应的节点newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}

解释请看图片和代码里面的注解,非常详细。 当链表里面没有节点,只有头节点的时候,上面代码依旧成立。


4.2 从头部插入节点的函数

//头插函数
void ListPushFront(ListNode* phead,ListDataType x)
{assert(phead);//断言函数:防止忘记初始化创建头节点ListNode* first = phead->next;//头插前,通过头节点找到第一个节点firstListNode* newnode = BuyListNode(x);//创建需要插入的新节点newnode->next = first;//使节点phead、first、newnode里的prev或next指向对应的节点first->prev = newnode;phead->next = newnode;newnode->prev = phead;
}

解释请看图片和代码里面的注解,非常详细。 当链表里面没有节点,只有头节点的时候,上面代码依旧成立。


5、尾删和头删减少节点的函数

5.1 从尾部删除节点的函数

//尾删函数
void ListPopBack(ListNode* phead)
{assert(phead);//断言函数:防止忘记初始化创建头节点if (phead->next != phead)//防止链表里一个节点都没有,就不需要删除{ListNode* tail = phead->prev;//通过头节点phead找到最后一个节点tailListNode* prev = tail->prev;//再通过最后一个节点tail找到其前面的节点prevfree(tail);//释放最后一个节点的空间tail == NULL;prev->next = phead;//使节点phead、prev里的指针prev或next指向对应的节点phead->prev = prev;}
}

解释请看图片和代码里面的注解,非常详细。 当链表里面只有一个节点时候,上面代码依旧成立。


5.2 从头部删除节点的函数

//头删函数
void ListPopFront(ListNode* phead)
{assert(phead);//断言函数:防止忘记初始化创建头节点if (phead->next != phead)//防止链表里一个节点都没有,就不需要删除{ListNode* first = phead->next;//通过头节点phead找到第一个节点firstListNode* second = first->next;//再通过第一个节点first找到第二个节点secondfree(first);//释放最后一个节点的空间first = NULL;phead->next = second;//使节点phead、second里的指针prev或next指向对应的节点second->prev = phead;}
}

解释请看图片和代码里面的注解,非常详细。 当链表里面只有一个节点时候,上面代码依旧成立。


6、提供一个数据,找到这个数据所在的节点的函数

//提供一个数据,找到这个数据所在的节点的函数
ListNode* ListFindData(ListNode* phead,ListDataType x)
{assert(phead);ListNode* cur = phead->next;while (cur != phead)//遍历:循环最后一个节点里的指针next,next存储的是头节点phead的地址{if (cur->data == x){printf("找到了这个节点\n");return cur;}else{cur = cur->next;}}printf("找不到这个节点\n");return NULL;
}

解释请看图片和代码里面的注解,非常详细。


7、给一个数据,在其所在节点前面插入一个节点的函数

此功能要和上面的提供一个数据,找到这个数据所在的节点的函数配合使用才能实现。

//给一个数据,在其所在节点前面插入一个节点的函数
void ListErase(ListNode* phead, ListDataType x, ListDataType y)//x是查找的节点数据,y是新节点的数据
{assert(phead);ListNode* pos = ListFindData(phead, x);if (pos != NULL)//防止没找到节点,就不用继续插入{ListNode* prev = pos->prev;//通过节点pos找到它前面的节点prevListNode* newnode = BuyListNode(y);//创建新节点newnode->prev = prev;//使节点prev、newnode、pos里的指针prev或next指向对应的节点prev->next = newnode;newnode->next = pos;pos->prev = newnode;}
}

解释请看图片和代码里面的注解,非常详细。

8、给一个数据,删除这个数据所在的节点的函数

此功能要和上面的提供一个数据,找到这个数据所在的节点的函数配合使用才能实现。

//给一个数据,删除这个数据所在的节点的函数
void ListInsert(ListNode* phead, ListDataType x)
{assert(phead);ListNode* pos = ListFindData(phead, x);if (pos != NULL)//防止没找到节点,就不用删除{ListNode* prev = pos->prev;//通过节点pos找到它前面的节点prevListNode* next = pos->next;//通过节点pos找到它后面的节点nextfree(pos);//释放节点pos的空间pos = NULL;prev->next = next;//使节点prev、next里的指针prev或next指向对应的节点next->prev = prev;}
}

解释请看图片和代码里面的注解,非常详细。


9、修改数据的函数

此功能要和上面的提供一个数据,找到这个数据所在的节点的函数配合使用才能实现。

//修改数据的函数
void ListChangeData(ListNode* phead,ListDataType x)
{int data = 0;//定义想要改成的数据assert(phead);ListNode* cur = phead->next;while (cur != phead)//遍历:循环最后一个节点里的指针next,next存储的是头节点phead的地址{if (cur->data == x){printf("找到了这个数据,请输入想要改成的数据:");scanf("%d", &data);cur->data = data;break;}else{cur = cur->next;}}if (cur == phead){printf("想要修改的数据不存在\n");}
}

解释请看图片和代码里面的注解,非常详细。


10、部分功能的简化与升级

10.1 尾插和头插函数的简化升级

//尾插函数
void ListPushBack(ListNode* phead, ListDataType x)
{ListInsert(phead, x);
}

尾插可以理解为在头节点pjead前面插入,因为链表是循环的,从肉眼看感觉放在了头节点的前面,但是鲜果上是尾插。


//头插函数
void ListPushFront(ListNode* phead, ListDataType x)
{ListInsert(phead->next, x);
}

头插就是在第一个节点前插入,头节点里的指针next存放的就是第一个节点的地址。


四、总代码

这里是总代码的地址所在,有需要的可以自取。

放代码: 代码 - Gitee.com


五、代码运行实例

数据结构:带头双向循环链表——增加、删除、查找、修改,详细解析相关推荐

  1. 数据结构-带头双向循环链表(增删查改详解)

    在上一篇博客中,详细介绍了单链表的增删查改,虽然单链表的结构简单,但是用起来却不是那么顺手.因此根据单链表的种种缺点,这篇博客所介绍的带头双向循环链表将会带来极大的优化. 上图就是带头双向循环链表的主 ...

  2. 【数据结构初阶】链表(下)——带头双向循环链表的实现

    目录 带头双向循环链表的实现 1.带头双向循环链表的节点类型 2.创建带头双向循环链表的节点 3.向带头双向循环链表中插入数据 <3.1>从链表尾部插入数据 <3.2>从链表头 ...

  3. 数据结构与算法 | 带头双向循环链表

    上一节里实现的是最简单的链表,在实际中那种链表不会单独用来存储数据,更多是作为其他数据结构的子结构,如图的邻接表等.而比较常用的就是带头双向循环链表. 通过对比我们可以看出有三个不同,多了头节点,链表 ...

  4. 【数据结构】-关于带头双向循环链表的增删查改

    作者:低调 作者宣言:写好每一篇博客 文章目录 前言 一.带头双向循环链表的实现 1.1创建返回链表的头结点 1.2开辟一个新的结点 1.3双向链表的销毁 1.4双向链表的打印 1.5双向链表尾插 1 ...

  5. 【数据结构】带头双向循环链表的增删查改(C语言实现)

    文章目录 前言 一.什么是带头双向循环链表 二.带头双向循环链表的实现 1.结构的定义 2.链表的初始化 3.开辟新节点 4.在头部插入数据 5.在尾部插入数据 6.查找数据 7.在pos位置之前插入 ...

  6. 比特数据结构与算法(第二章收尾)带头双向循环链表的实现

    1.链表的分类 链表的分类 ① 单向或者双向 ② 带头或者不带头 ③ 循环或者非循环 常用的链表: 根据上面的分类我们可以细分出8种不同类型的链表,这么多链表我们一个个讲解这并没有意义.我们实际中最常 ...

  7. 【数据结构】链表:带头双向循环链表的增删查改

    本篇要分享的内容是带头双向链表,以下为本片目录 目录 一.链表的所有结构 二.带头双向链表 2.1尾部插入 2.2哨兵位的初始化 2.3头部插入 2.4 打印链表 2.5尾部删除 2.6头部删除 2. ...

  8. 初阶数据结构之带头+双向+循环链表增删查实现(三)

    文章目录 @[TOC](文章目录) 前言 一.带头双向循环链表的初始化 1.1带头双向循环链表的结构体定义 1.2初始化代码的实现 二.带头+双向+循环链表的增功能实现 2.1头插代码的实现 2.2尾 ...

  9. 【数据结构】带头双向循环链表

    各位读者们好久不见了,咋们接着上一期链表来,今天来实现一下链表最难的结构,同时也是实现起来最简单的结构--带头双向循环链表.话不多说,进入主题 文章目录 前言 实现带头双向循环链表 DList.h头文 ...

最新文章

  1. 20. Valid Parentheses
  2. LIS ZOJ - 4028
  3. 解决Eclipse狂吃内存的解决方法(转)
  4. textureview 缩放_用MediaPlayer+TextureView封装一个完美实现全屏、小窗口的视频播放器...
  5. rog live service是什么_王者荣耀五周年好礼送不停,玩游戏还能白嫖ROG游戏手机3?...
  6. 计算机网络是互相连接的自治系统,自治系统内ip子网和sdn子网的互联机制imisa-江苏计算机网络.pdf...
  7. Halcon例程详解(基于卡尺工具的匹配测量方法) —— measure_stamping_part.hdev
  8. 两数的和与差的简单函数
  9. include.cpp(main函数的cpp文件)文件中包含另一个.cpp文件的错误及原因
  10. [20170927]关于hugepages.txt
  11. win10隐藏linux,Win10如何隐藏Windows Defender任务栏图标
  12. 【原创】php+mysql下,对网站架构方面的一些认识(以我维护的站点为例)
  13. 【java】java 如何抛出 sleep interrupted
  14. 是什么让南浔银行动了心?“高端存储+双活”
  15. Redis的安装及原理介绍
  16. 如何使用SwitchyOmega.crx谷歌插件
  17. 成功是需要付出代价的: 32个成功观念分享
  18. android字体等宽,Android等宽字体大小
  19. Ajax学习笔记-get请求参数-3
  20. 实例079RTF文件的保存

热门文章

  1. 程序员与英语:即时聊天中的英语缩写 lol / lmao / idk
  2. XMPP中文 XEP-0060: 发布-订阅(一)
  3. SpaceX 推出星链奖励计划,最高赏金2.5万美元
  4. Codeforces Round 870 (Div. 2)【A、B、C、D】
  5. 取消大小周,就能换来周末自由吗?
  6. html不鼓励使用的标签,鼓励和提醒孩子使用敬语.html
  7. 创作者经济序幕拉开,「中国版Figma」Pixso靠什么出位?
  8. HNU-电路与电子学-小班2
  9. 摩尔定律要失效了吗?
  10. 最优化基础理论与方法——2.1最优性条件