c语言-数据结构-带头双向循环链表

       

目录

1、双向循环链表的结构

2、双向循环链表的结构体创建

3、双向循环链表的初始化

3.1 双向链表的打印

 4、双向循环链表的头插

 5、双向循环链表的尾插

6、双向循环链表的删除

6.1 尾删

6.2 头删

6.3 小节结论        

7、查找

8、在pos位置前插入数据

9、删除pos位置的数据

10、释放双向循环链表

结语:


前言:

         双向循环链表在实际应用中是一种非常广泛的数据结构,双向循环链表在结构上比单链表更复杂,比如单链表中的节点只有一个指针,而双向循环链表中有两个指针共同维护该节点,其中一个指针指向后一个节点,而另一个指针指向前面一个节点。虽然其结构较复杂,但是在实现增删查改的功能上却比单链表要便捷的多。

1、双向循环链表的结构

        上图的第一个节点head称为该链表的头节点(也称为哨兵位的节点,他的作用就好比一个哨兵只负责站岗),该节点中与其他节点的结构一样,只是该头节点中的数据不具有有效性,也就是打印数据时除了头节点的数据不打印,其他节点的数据都要打印。 

        该头节点的优势在于无需更改pilst的指向,因为plist指针始终指向该节点,只需要对该节点内部成员的指针进行修改即可,同时也可以简化代码,具体如下文。

2、双向循环链表的结构体创建

        节点的结构体代码如下:

typedef int DListDataType;//int类型重定义,方便使用其他类型时进行修改typedef struct DoubleListNode
{struct DoubleListNode* prev;//prev指向前一个节点的指针struct DoubleListNode* next;//next指向后一个节点的指针DListDataType data;//存储的数据
}DLNode;//重定义结构体类型

3、双向循环链表的初始化

       初始化即生成一个头节点,即哨兵位节点,并且用一个指针指向其节点即可。因为是循环链表,因此初始化的时候要将头节点的两个指针都指向自己。

         因此当链表为空的时候,头节点(哨兵位节点)依然是存在的,则在对链表进行删除操作时头节点不能被删除。 

        初始化代码如下:

DLNode* DLInit()//初始化
{DLNode* phead = BuyNode(-1);//节点创建函数phead->next = phead;//只有一个头节点的时候也是自己指向自己phead->prev = phead;return phead;//返回头节点的地址
}int main()
{DLNode* plist = DLInit();//外部有一个结构体指针来接收返回值return 0;
}

3.1 双向链表的打印

        打印双向链表,以便观察各个功能的结果:

void Print(DLNode* phead)//打印
{assert(phead);DLNode* cur = phead->next;printf("哨兵位<=>");while (cur != phead){printf("%d<=>", cur->data);cur = cur->next;}
}

 4、双向循环链表的头插

         思路很简单,就是将newnode节点的next指向d1,并且d1的prev指向newnode。newnode的prev指向head,head的next指向newnode。但是这里涉及到节点的创建,每次插入节点的时候都需要创建节点,因此将其封装成一个函数如下:

DLNode* BuyNode(DListDataType x)
{DLNode* newnode = (DLNode*)malloc(sizeof(DLNode));//malloc开辟空间if (newnode == NULL)//判断malloc是否成功{perror("BuyNode");return NULL;}newnode->data = x;//赋予创建节点的值newnode->prev = NULL;//置空newnode->next = NULL;//置空return newnode;//返回节点的地址
}

        头插代码: 

void PushFront(DLNode* phead, DListDataType x)//头插
{assert(phead);DLNode* next = phead->next;//定义一个指针next,他指向phead的下一个节点DLNode* newnode = BuyNode(x);//创建节点phead->next = newnode;//phead下一个节点为newnodenewnode->prev = phead;//newnode前一个节点为pheadnewnode->next = next;//newnode下一个节点为nextnext->prev = newnode;//next前一个节点为newnode
}

 5、双向循环链表的尾插

        思路与头插差不多,但是尾插能体现出双向循环链表的优势在于无需遍历整个链表去找尾,因为head的prve指向的就是尾, 因此可以直接找到尾,然后再进行尾插操作。

        尾插代码如下:

void PushBack(DLNode* phead, DListDataType x)//尾插
{assert(phead);DLNode* tail = phead->prev;//找到尾部节点DLNode* newnode = BuyNode(x);//创建节点tail->next = newnode;//让尾部的下一个节点为newnodenewnode->prev = tail;//让newnode的上一个节点为tailphead->prev = newnode;//让phead的上一个节点为newnodenewnode->next = phead;//让newnode下一个节点是phead
}

6、双向循环链表的删除

6.1 尾删

         思路:从上文可以得知,找到尾节点很简单,但是这里需要再定义一个指针,他指向tail前一个节点。然后将tail释放后,再把tailprev的next指向头节点,头节点的prev更新成指向tailprev节点即可完成尾删。

        尾删代码如下:

bool Empty(DLNode* phead)//判断链表是否为空
{assert(phead);return phead->next == phead;//为空返回真
}void PopBack(DLNode* phead)//尾删
{assert(phead);assert(!Empty(phead));//若返回真表示链表为空,则结果取反,断言不通过DLNode* tail = phead->prev;//定义tail指针DLNode* tailPrev = tail->prev;//定义tailPrev指针tailPrev->next = phead;//tailPrev下一个节点为pheadphead->prev = tailPrev;//让phead的前一个节点为tailPrevfree(tail);//释放tail节点
}

6.2 头删

         思路:将头节点与second指向的节点互相联系起来,然后将first释放即可。

        头删代码如下:

void PopFront(DLNode* phead)//头删
{assert(phead);assert(!Empty(phead));//链表判空DLNode* first = phead->next;//定义指针DLNode* second = first->next;phead->next = second;//将头节点于second节点相连second->prev = phead;free(first);//删除first节点
}

6.3 小节结论        

        这里可以体现出双向循环链表相对于单链表的一个优势:链表只有一个节点的时候,直接删除即可无需将plist指针置为空,因为plist指针始终指向哨兵节点。若是单链表进行删除则还要进行多一项的判断,还要考虑plist指针是否为空的情况。

        但是其缺陷是空链表的时候若还进行删除会把哨兵位也删了,这时候plist就是野指针,对pilst进行解引用就会出现非法访问的问题。因此要断言链表是否为空,为空则不能进行删除。 

7、查找

        查找功能就相对简单点,只需要遍历链表,返回要查找节点的地址pos即可,只是这里需要注意一点,即:遍历链表的条件,因为头节点的数据没有有效性,因此从头节点的下一个节点开始遍历,直到遍历到头节点结束。

        查找代码如下:

DLNode* Seach(DLNode* phead, DListDataType x)//搜索
{assert(phead);DLNode* cur = phead->next;//用cur代替phead去遍历while (cur != phead)//cur指向头节点时结束循环{if (cur->data == x)return cur;//如果等于返回cur地址cur = cur->next;//遍历cur}return NULL;//链表中没有该数据则返回空
}

        查找函数通常与中间插入、中间删除函数进行搭配,因为查找函数返回了一个地址pos,再将这个地址直接传到中间插入、中间删除函数内,就能很好的实现功能。

8、在pos位置前插入数据

         思路:通过查找函数可以得到pos位置的地址,然后再定义一个指针prev,再将newnode节点与prev节点和pos节点联系起来即可。

        pos前插代码如下:

void PosInsert(DLNode* pos, DListDataType x)//在pos前插入
{assert(pos);DLNode* posPrev = pos->prev;//定义posprev指针,指向pos前面一个节点DLNode* newnode = BuyNode(x);//创建节点newnode->prev = posPrev;//以下的操作就是把newnode节点与pos、posprev节点联系起来posPrev->next = newnode;newnode->next = pos;pos->prev = newnode;
}

        插入数据与查找数据搭配测试代码如下,随便测试之前的接口:

int main()
{DLNode* plist = DLInit();//初始化PushFront(plist, 4);//头插PushFront(plist, 3);PushFront(plist, 2);PushFront(plist, 1);PushBack(plist, 5);//尾插PushBack(plist, 6);/*PopFront(plist);PopBack(plist);*/Print(plist);//打印DLNode* pos = Seach(plist, 1);//查找if (pos){PosInsert(pos, 20);//中间插入}printf("\n");//换行Print(plist);//打印DLNodeFree(plist);//释放链表plist = NULL;//手动将plist置空return 0;
}

        运行结果:

9、删除pos位置的数据

        思路:将posPrev与posNext这两个节点联系起来,然后释放pos即可。

        删除pos位置代码如下:

void PosDestroy(DLNode* pos)//删除pos位置
{assert(pos);DLNode* posPrev = pos->prev;//定义两个指针,一个指向pos前面节点,一个指向pos后面节点DLNode* posNext = pos->next;posPrev->next = posNext;//将这两个指针指向的节点联系在一起posNext->prev = posPrev;free(pos);//释放pos节点
}

10、释放双向循环链表

        因为链表中的各个节点(包括哨兵位节点)都是在堆上申请的,因此在使用完毕后应该对这些空间进行释放。

        释放函数代码如下:

void DLNodeFree(DLNode* phead)//释放链表
{assert(phead);//此处要断言,不然下面会对空指针解引用DLNode* cur = phead->next;//用cur指针代替phead去遍历while (cur != phead){//Next指针的作用是记住位置,防止释放cur后找不到下一个节点DLNode* Next = cur->next;free(cur);//释放cur指向的节点cur = Next;//赋予cur新的位置}free(phead);//最后释放哨兵位(头节点)
}int main()
{DLNode* plist = DLInit();DLNodeFree(plist);plist = NULL;//外部的plist此时的野指针,要手动置空return 0;
}

        这里注意的点:若在DLNodeFree函数内部进行对phead的置空,则不会影响外部plist的值,因为phead只是plist的一份临时拷贝,除非将pilst的地址传给DLNodeFree函数,用二级指针来操作,或者手动在外面将plist置空,两种方法都能将plist置空。

结语:

        以上就是关于双向循环链表的实现与解析,如果本文对你起到了帮助,希望可以点赞👍+关注😎+收藏👌哦!如果有遗漏或者有误的地方欢迎大家在评论区补充~!!谢谢大家!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/170213.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Lightroom Classic 2023 v12.4

Lightroom Classic 2023是一款图像处理软件&#xff0c;是数字摄影后期制作的重要工具之一。与其他图像处理软件相比&#xff0c;Lightroom Classic具有以下特点&#xff1a; 高效的图像管理&#xff1a;Lightroom Classic提供了强大的图像管理功能&#xff0c;可以轻松导入、…

Google Firebase PHP实现消息推送

获取key的方法&#xff1a; 登录谷歌开发者后台 https://console.firebase.google.com/?hlzh-cn function firebaseNotice($title,$body){$token_arr[token1,token2]; //用户的firebasetoken列表$notify_msg ["notification" > ["title" > $title…

古人是如何防雷的?——中国古建筑中的防雷智慧

“雷电”这个词自古以来就一直伴随着人类的生活&#xff0c;并且给我们人类的生活造成了不小的影响。我国古代建筑大多是木质结构&#xff0c;如果雷击打在上面&#xff0c;就不仅仅是物理损坏&#xff0c;还有可能引发火灾。为了保护建筑物及人们的生命财产安全&#xff0c;如…

PostgreSQL 入门教程

PostgreSQL 入门教程 1. 历史背景2. 概念3. 特点4. 用法4.1 数据库连接4.2 数据库创建4.3 表创建4.4 数据插入4.5 数据查询4.6 数据更新4.7 数据删除 5. 安装步骤6. 简单示例7. 扩展7.1 数据类型7.2 查询优化7.3 并发控制7.4 数据备份和恢复7.5 扩展性和高可用性7.6 安全性加固…

【uniapp/uview】Collapse 折叠面板更改右侧小箭头图标

最终效果是这样的&#xff1a; 官方没有给出相关配置项&#xff0c;后来发现小箭头不是 uview 的图标&#xff0c;而是 unicode 编码&#xff0c;具体代码&#xff1a; // 箭头图标 ::v-deep .uicon-arrow-down[data-v-6e20bb40]:before {content: \1f783; }附一个查询其他 u…

第二十九章 目标检测中的测试模型评价指标(车道线感知)

前言 近期参与到了手写AI的车道线检测的学习中去&#xff0c;以此系列笔记记录学习与思考的全过程。车道线检测系列会持续更新&#xff0c;力求完整精炼&#xff0c;引人启示。所需前期知识&#xff0c;可以结合手写AI进行系统的学习。 介绍 自动驾驶的一大前提是保证人的安全…

IntelliJ IDEA 2023.2.1 (Ultimate Edition) 版本 Git 如何合并多次的本地提交进行 Push

本心、输入输出、结果 文章目录 IntelliJ IDEA 2023.2.1 (Ultimate Edition) 版本 Git 如何合并多次的本地提交进行 Push前言为什么需要把多次本地提交合并合并提交的 2 种形式:事中合并、事后合并事中合并事后合并:支持拆分为多组提交弘扬爱国精神IntelliJ IDEA 2023.2.1 (U…

中小企业数字化转型进程加速,CRM系统前景如何?

自疫情不断反复之后&#xff0c;中小企业数字化转型进程开始加速。作为当下最热门的企业级应用&#xff0c;CRM客户管理系统的前景还是被看好的。相比于美国企业CRM系统7成的使用率&#xff0c;中国的CRM市场还有很大的发展空间。下面来详细说说&#xff0c;CRM系统的前景如何&…

深度学习AI识别人脸年龄

以下链接来自 落痕的寒假 GitHub - luohenyueji/OpenCV-Practical-Exercise: OpenCV practical exercise GitHub - luohenyueji/OpenCV-Practical-Exercise: OpenCV practical exercise import cv2 as cv import time import argparsedef getFaceBox(net, frame, conf_thresh…

Django中Cookie和Session的使用

目录 一、Cookie的使用 1、什么是Cookie&#xff1f; 2、Cookie的优点 3、Cookie的缺点 4、Django中Cookie的使用 二、Session的使用 1、什么是Session&#xff1f; 2、Session的优点 3、Session的缺点 4、Django中Session的使用 三、Cookie和Session的对比 总结 D…

Android修行手册-POI操作Excel实现超链接并且变为蓝色

点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册点击跳转>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&…

相机突然断电,保存的DAT视频文件如何打开

3-6 本文主要解决因相机突然断电导致拍摄的视频文件打不开的问题。 在平常使用相机拍摄视频&#xff0c;比如使用佳能相机拍摄视频的时候&#xff0c;如果电池突然断电&#xff0c;就非常有可能会导致视频没来得及保存而损坏的情况&#xff0c;比如会产生下图中的这种DAT文件…