双向带头循环链表详解
在上一篇所讲述的 单链表 中,存在一些缺陷:
- 1、在进行尾插和尾删时,需要遍历链表找到尾结点
- 2、在进行中间插入和删除时,也需要先遍历链表找到前一个结点
对于这些缺陷,可以采用一种结构更为复杂的链表 双向带头循环链表
双向带头循环链表结构虽然复杂,但在链表的操作上带来了很大的优势
文章目录
- 一、双向带头循环链表的结构
- 二、双向带头循环链表的函数接口
- 1. 申请结点
- 2. 初识化
- 3. 打印
- 4. 尾插尾删
- 5. 头插头删
- 6. 查找
- 7. 中间插入和删除
- 8. 判空及求链表长度
- 9. 销毁单链表
一、双向带头循环链表的结构
//存储数据的类型,这里以 int 来举例
typedef int LTDataType;//结点的类型
typedef struct ListNode
{LTDataType data;struct ListNode* prev;struct ListNode* next;
}LTNode;
二、双向带头循环链表的函数接口
1. 申请结点
在插入等操作时需要申请结点,为了避免麻烦重复的操作,这里将申请结点封装为一个函数
申请结点函数如下:
LTNode* BuyLTNode(LTDataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){//开辟空间失败,打印错误信息perror("malloc");//结束程序exit(-1);}newnode->data = x;newnode->prev = newnode->next = NULL;return newnode;
}
2. 初识化
在双向带头循环链表中,即使没有存储数据也 至少会包含一个哨兵位的头结点
初始化函数如下:
LTNode* InitLT()
{//申请头结点,头结点的数据存什么无关紧要LTNode* phead = BuyLTNode(-1);//改变指针指向,构成循环phead->prev = phead->next = phead;return phead;
}
3. 打印
为了验证插入、删除等得到的结果是否正确,提供打印函数,这里数据类型以 int 为例,当读者采用的类型不同时,自行更改函数即可
打印函数如下:
void LTPrint(LTNode* phead)
{//链表不能为空assert(phead);LTNode* cur = phead->next;printf("head->");while (cur != phead){printf("%d->", cur->data);cur = cur->next;}printf("head\n");
}
4. 尾插尾删
尾插:在链表的最后一个结点之后插入结点
尾插函数如下:
void LTPushBack(LTNode* phead, LTDataType x)
{//链表不能为空assert(phead);LTNode* newnode = BuyLTNode(x);//找到尾结点LTNode* tail = phead->prev;//改变指针指向tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}
尾删:删除链表最后一个结点
尾删函数如下:
void LTPopBack(LTNode* phead)
{assert(phead); //链表不能为空assert(phead->next != phead); //空链表不能删//找尾结点及尾结点的前一个结点LTNode* tail = phead->prev;LTNode* tailPrev = tail->prev;//改变指针指向tailPrev->next = phead;phead->prev = tailPrev;free(tail);
}
5. 头插头删
头插: 在第一个结点之前插入新结点
头插函数如下:
void LTPushFront(LTNode* phead, LTDataType x)
{//链表不能为空assert(phead);LTNode* newnode = BuyLTNode(x);//找到头结点后的第一个结点LTNode* first = phead->next;//改变指针指向phead->next = newnode;newnode->prev = phead;newnode->next = first;first->prev = newnode;
}
头删:删除链表的第一个结点
头删函数如下:
void LTPopFront(LTNode* phead)
{assert(phead); //链表不能为空assert(phead->next != phead); //空链表不能删//找到头结点后的第一个和第二个结点LTNode* first = phead->next;LTNode* second = first->next;//改变指针指向phead->next = second;second->prev = phead;free(first);
}
6. 查找
查找:如果数据存在,返回该数据结点的指针,不存在返回 NULL
查找函数如下:
LTNode* LTFind(LTNode* phead, LTDataType x)
{//链表不能为空assert(phead);LTNode* cur = phead->next;while (cur != phead){if (cur->data == x) return cur;cur = cur->next;}return NULL;
}
7. 中间插入和删除
中间插入:通过查找函数 LTFind 获得指向结点的指针 pos,在 pos 指向的 结点之前 插入结点
在 pos 之前插入结点函数如下:
void LTInsert(LTNode* pos, LTDataType x)
{//pos 不能为空assert(pos);LTNode* newnode = BuyLTNode(x);//找到 pos 的前一个结点LTNode* posPrev = pos->prev;//改变指针指向posPrev->next = newnode;newnode->prev = posPrev;newnode->next = pos;pos->prev = newnode;
}
在调用中间插入函数 LTInsert 时
- 如果在链表头结点之前插入数据,便和尾插函数的功能一样
- 如果在链表头结点之后插入数据,便和头插函数的功能一样
因此在尾插和头插函数的实现中可以直接调用中间插入函数 LTInsert
尾插和头插函数更改如下:
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{//链表不能为空assert(phead);LTInsert(phead, x);
}//头插
void LTPushFront(LTNode* phead, LTDataType x)
{//链表不能为空assert(phead);LTInsert(phead->next, x);
}
中间删除:通过查找函数 LTFind 获得指向结点的指针 pos,删除 pos 指向的结点
删除 pos 指向的结点函数如下:
void LTErase(LTNode* pos)
{//pos 不能为空assert(pos);//找到 pos 的前一个和后一个结点LTNode* posPrev = pos->prev;LTNode* posNext = pos->next;//改变指针指向posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}
在调用中间删除函数 LTErase 时
- 如果删除链表头结点的前一个结点,便和尾删函数的功能一样
- 如果删除链表头结点的后一个结点,便和头删函数的功能一样
因此在尾删和头删函数的实现中可以直接调用中间删除函数 LTErase
尾删和头删函数更改如下:
//尾删
void LTPopBack(LTNode* phead)
{assert(phead); //链表不能为空assert(phead->next != phead); //空链表不能删LTErase(phead->prev);
}//头删
void LTPopFront(LTNode* phead)
{assert(phead); //链表不能为空assert(phead->next != phead); //空链表不能删LTErase(phead->next);
}
8. 判空及求链表长度
判空:判断链表是否为空
判空函数如下:
bool LTEmpty(LTNode* phead)
{//链表不能为空assert(phead);return phead->next == phead;
}
链表长度:链表有效数据个数
链表长度函数如下:
size_t LTSize(LTNode* phead)
{//链表不能为空assert(phead);size_t size = 0;LTNode* cur = phead->next;while (cur != phead){size++;cur = cur->next;}return size;
}
9. 销毁单链表
在链表中,存储数据的结点是由自己开辟的,当不使用链表时,应将其销毁
销毁链表函数如下:
void LTDestroy(LTNode* phead)
{//链表不能为空assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* curNext = cur->next;free(cur);cur = curNext;}free(phead);
}
双向带头循环链表详解相关推荐
- GPIO输入输出各种模式(推挽、开漏、准双向端口)详解---重点双向开漏端口
GPIO输入输出各种模式(推挽.开漏.准双向端口)详解 原创techexchangeischeap 发布于2017-05-20 10:31:31 阅读数 62961 收藏 展开 技术交流是便宜的博客 ...
- 【数据结构初阶】双向带头循环链表原来是纸老虎,结构复杂,操作简单
目录 0.结构体定义 1.初始化 2.尾插 3.打印 4.头插 5.任意位置插入前面位置 6.尾删 7.头删 8.链表长度 9.任意位置删除当前位置 10. 销毁 双向带头循环链表:结构复杂,操作简单 ...
- 链表界的“扛把子”—双向带头循环链表
目录 一:链表的分类 二:链表的实现 2-1:创建链表 2-2:创建新的结点 2-3:初始化链表 2-4:打印链表 2-5:销毁链表 三:链表的核心功能 3-1:尾插 3-2:尾删 3-3:链表查找 ...
- 双向带头循环链表的(增删查改)的实现
文章目录 一.双向带头循环链表 构成 二.双向带头循环链表的实现 1.函数的定义和结构体的创建--list.h 2.函数的调用--list.c 3. 双向带头循环链表与单链表的传递参数区别 4.双向带 ...
- 数据结构------双向带头循环链表
目录 1.什么是双向带头循环链表?模型. 2.1 创建结点函数 2.2初始化 3.尾插实现 3.1图示 3.2尾插 4.打印函数实现 4.1打印函数 5.思考为啥是一级指针 6.头插实现 6.2实现代 ...
- 双向带头循环链表的实现
双向带头循环链表 双向带头循环链表 结构讲解 期望实现功能 创建链表和头节点作用 头插和头删 头插 头删 尾插与尾删 尾插 尾删 pos 删除和插入 插入 删除 打印和查找 整体代码 这个数据结构可以 ...
- 外强中干——双向带头循环链表
前言:众所周知,链表有八种结构,由单向或双向,有头或无头,循环或不循环构成.在本篇,将介绍8种链表结构中最复杂的--双向带头循环链表.听着名字或许挺唬人的,但实际上双向带头循环链表实现起来比结构最简单 ...
- 【双向带头循环链表】
双向带头循环链表 0.链表中每个节点的基本结构 1.初始化链表(创建哨兵位头节点) 2.申请新节点 3.尾插节点 4.尾删节点 5.头插节点 6.头删节点 7.寻找pos位置的节点 8.在pos位置之 ...
- 双向带头循环链表-实现思路+图解
目录 一.何为双向循环链表? 1.何为'双向'? 2.何为'带头'? 3.何为'循环'? 二.如何实现双向带头循环链表? 1.基本结构-结点的创建 2.创建哨兵位结点 3.链表的增删查改 一.何为双向 ...
最新文章
- java中Integer装箱的注意
- cmake 2.8.12在redhat 4.4下安装
- Swift之深入解析基于闭包的类型擦除
- android通讯录管理(获取联系人,通话记录,短信消息),Android通讯录管理(获取联系人、通话记录、短信消息)(二)...
- 鸿蒙首饰用什么合成,天下HD鸿蒙炼炉是什么?鸿蒙炼炉讲解
- Find 3-friendly Integers
- (王道408考研操作系统)第四章文件管理-第一节4:文件物理结构(文件分配方式)
- 容器安全 - 通过SECCOMP过滤在容器中的风险操作
- 使用Iocomp工控图表工具绘制实时曲线
- java实现代码在线编译器-从零开发(一)简单本地编译+运行测试
- Ubuntu命令方式安装中文语言包
- 编译原理实验一:单词的词法分析程序设计
- Unity与Android权限设置
- 从网站细节入手提高易用性
- 《SQL进阶教程》学习
- 国网大数据中心是如何进行数据治理的?
- 分享32套精美的免费 PSD 网页界面设计素材
- scheduling agreement 计划协议详解
- 3 个月前被裁员了,心情跌落谷底,直到我看到了这本神书…
- skype8.34 新版本不能启动/打开