【双向带头循环链表】
双向带头循环链表
0.链表中每个节点的基本结构
1.初始化链表(创建哨兵位头节点)
2.申请新节点
3.尾插节点
4.尾删节点
5.头插节点
6.头删节点
7.寻找pos位置的节点
8.在pos位置之前插入节点
9.删除pos位置的节点
10.打印链表出来
11.销毁链表
在链表中有八大形式,如下图:
- 其中,最为重要的两种结构为最简单的单向不带头不循环链表和双向带头循环链表。 单链表前面的文章已经介绍过,本篇文章重点介绍双向带头循环链表。
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。
另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
双向带头循环链表的基本结构如上图:
在单链表的基础上,新增了哨兵位的头节点,头节点不存储有效数据,还新增了一个指针prev,用于链接节点之前的节点。
节点的结构
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>typedef int LTDateType;typedef struct ListNode
{LTDateType data;struct ListNode* prev;struct ListNode* next;
}LTNode;
所以每一个节点的结构如上,下面来逐一实现双向带头循环的接口函数:
1.初始化双向链表(创建哨兵位头节点)
LTNode* ListInit()
{LTNode* phead = (LTNode*)malloc(sizeof(LTNode));assert(phead);phead->data = 0;phead->next = phead;phead->prev = phead;return phead;
}
也可以按照自己的需求,可以不在ListInit内部创建带哨兵位的头节点,也可以使用其他的方式来判断节点是否申请成功,不过建议初学者模仿别人优秀的代码。
2.申请新节点
LTNode* BuyLTNode(LTDateType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));assert(newnode);newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
- 由于在头插尾插和在pos位置插入节点时需要申请节点空间,所以我们干脆把申请节点这件事情另外分装成一个函数,在头插尾插中直接调用该函数即可。
3.尾插节点
void ListPushBack(LTNode* phead, LTDateType x)
{assert(phead);LTNode* newnode = BuyLTNode(x);assert(newnode);LTNode* tail = phead->prev;newnode->prev = tail;tail->next = newnode;newnode->next = phead;phead->prev = newnode;}
尾插中,只需要把phead->prev 指向尾插的新节点,phead->next在指向newnode。newnode->prev指向头,newnode->next也指向头,这样就链接成了双向带头循环链表。
4.尾删节点
void ListPopBack(LTNode* phead)
{assert(phead);assert(phead->next != phead);//不能删哨兵位的头LTNode* Pop = phead->prev;LTNode* PoptPrev = Pop->prev;PoptPrev->next = phead;phead->prev = PoptPrev;free(Pop);}
尾删的时候,记录最后一个删除的节点的前面的节点,然后让头节点与该节点建立关系即可。
5.头插节点
void ListPushFront(LTNode* phead, LTDateType x)
{assert(phead);LTNode* newnode = BuyLTNode(x);assert(newnode);LTNode* cur = phead->next;cur->prev = newnode;newnode->next = cur;phead->next = newnode;newnode->prev = phead;}
头插也是如此,记录哨兵位的头节点的下一个节点,在哨兵位的头节点和该节点直接插入一个新节点,让它们形成链接关系即可。
6.头删节点
void ListPopFront(LTNode* phead)
{assert(phead);assert(phead->next!=phead);//不能删哨兵位的头LTNode* Pop = phead->next;LTNode* PopNext = Pop->next;phead->next = PopNext;PopNext->prev = phead;free(Pop);
}
头删,记录头删的节点的下一个节点,让头节点与该节点建立联系。
7.寻找pos位置的节点
LTNode* ListFind(LTNode* phead,LTDateType x)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){if (cur->data == x)return cur;cur = cur->next;}return NULL;
}
寻找节点很简单,遍历一遍链表即可。
8.在pos位置之前插入节点
//在pos位置之前插入
void ListInsert(LTNode* pos , LTDateType x)
{assert(pos);LTNode* posprev = pos->prev;LTNode* newnode = BuyLTNode(x);assert(newnode);posprev->next = newnode;newnode->prev = posprev;pos->prev = newnode;newnode->next = pos;}
注意:插入某个位置的节点,一般是和ListFind函数一起使用,先找到链表中的某个节点,再在该节点之前插入节点。
所以,头插和尾插,均可以用该插入函数来代替。
先写头插尾插的原因是,对于初学者,建议一步一步走。
对于尾插来说,给phead即可,因为现在设计的插入函数是在pos位置之前插入,phead位置之前就是尾。
void ListPushBack(LTNode* phead, LTDateType x)
{assert(phead);//phead->prev = newnode;ListInsert(phead, x);//可以复用插入函数
}
对于头插,更加简单,传phead->next即可。
void ListPushFront(LTNode* phead, LTDateType x)
{assert(phead);ListInsert(phead->next, x);//头插
}
9.删除pos位置的节点
void ListErase(LTNode* pos)
{assert(pos);LTNode* posprev = pos->prev;LTNode* posnext = pos->next;posprev->next = posnext;posnext->prev = posprev;free(pos);
}
同理,头删尾删也可以调用该函数来实现。
建议下去自己尝试一遍。
10.打印链表出来
void ListPrint(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){printf("%d->", cur->data);cur = cur->next;}printf("\n");
}
11.销毁链表
void ListDestroy(LTNode* phead)
{assert(phead);LTNode* cur = phead->next;while (cur != phead){LTNode* next = cur->next;free(cur);cur = next;}free(phead);phead = NULL;//这条语句没用,因为传进来的是形参phead//传二级指针才有用,但是为了接口函数的一致性,我们可以在调用完该函数再置空。//例如://ListDestroy(plist);//plist = NULL;
}
注意:最后释放带哨兵位的头节点时,置空的那条语句在该函数内部不起作用,因为传进来的是phead的拷贝,是形参,形参的改变不影响实参,所以在调用完该函数后,需要手动置空。
为什么不设计成传实参进来呢?其实也可以这样设计,不过我们前面的所以的接口函数都是传形参,即一级指针,突然传一个二级指针,难免感到奇怪违和,给读代码的人带来疑惑,所以我们这里也跟随接口函数的一致性来设计。
实现结果如下,完美达到目的。
【双向带头循环链表】相关推荐
- 【数据结构初阶】双向带头循环链表原来是纸老虎,结构复杂,操作简单
目录 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种链表结构中最复杂的--双向带头循环链表.听着名字或许挺唬人的,但实际上双向带头循环链表实现起来比结构最简单 ...
- 双向带头循环链表-实现思路+图解
目录 一.何为双向循环链表? 1.何为'双向'? 2.何为'带头'? 3.何为'循环'? 二.如何实现双向带头循环链表? 1.基本结构-结点的创建 2.创建哨兵位结点 3.链表的增删查改 一.何为双向 ...
- 双向带头循环链表详解
在上一篇所讲述的 单链表 中,存在一些缺陷: 1.在进行尾插和尾删时,需要遍历链表找到尾结点 2.在进行中间插入和删除时,也需要先遍历链表找到前一个结点 对于这些缺陷,可以采用一种结构更为复杂的链表 ...
- 【数据结构--双向带头循环链表(链表中的老大哥)】
双向带头循环链表及相关OJ
最新文章
- php exit、return、break、continue之间的差别,具体介绍
- 深入浅出设计模式之设计模式简介
- junit 内部类测试_Springboot 使用单元测试
- How to download ABAP development Tool from Fiori Launchpad
- 职场上个人的核心技术_职场上,这3种人表面老实,实际却是个“高手”,要远离...
- Haproxy安装及配置(转)
- Glances:一款功能强大的操作系统安全监控平台
- C#常用42个操作类
- Hibernate 1
- mysql复合语句声明开始于_mysql8 参考手册--BEGIN ... END复合语句
- 2017杭州云栖大会FAQ(持续更新中)
- linux~tar压缩文件夹到指定文件夹 --- 相对路径
- 资本为什么爱“吃面”?
- python生成树状图_python 字典生成树状图
- tableau 和sfdc连携提取数据时,发现数据提取出错误或不能全部提取数据。
- java gef_GEF简介
- 玩转Atlas 200DK(一)分设环境之开发环境准备
- pytorch实现word_embedding(negative_sampling的skip-gram模型)
- 【Spring之轨迹】结合 @Scheduled 实现定时将 Redis 缓存刷入数据库(配 Redis 工具类与例子实战)
- php fwrite 图片,PHP fwrite( )用法及代码示例