数据结构-带头双向循环链表(增删查改详解)
在上一篇博客中,详细介绍了单链表的增删查改,虽然单链表的结构简单,但是用起来却不是那么顺手。因此根据单链表的种种缺点,这篇博客所介绍的带头双向循环链表将会带来极大的优化。
上图就是带头双向循环链表的主要结构了,很多同学看到这个结构就想知难而退了,但是只要耐心深究,就会发现除了结构比单链表复杂以外,它的实现反而简单了!!!
目录
1 链表的结点结构
2 链表初始化
3 链表打印
4 链表的增删查改
4.1 链表尾插
4.2 链表尾删
4.3 链表头插
4.4 链表头删
4.4 查找链表中的值
4.5 在对应位置前面插入
4.6 删除对应位置的结点
5 链表销毁
6 完整代码及运行结果
1 链表的结点结构
typedef struct DListNode
{
struct DListNode* next;
struct DListNode* prev;
DLTDataType val;
}DLTNode;
细心的同学肯定会发现,这次链表中的结构相比于单链表多了一个prev的结构体指针,有了它的 存在就能实现双向的功能了!
2 链表初始化
在初始化链表前我们需要创建一个哨兵卫的头结点,要创建结点,那依旧少不了结点创建函数。
DLTNode* BuyListNode(DLTDataType x)
{DLTNode* newnode = (DLTNode*)malloc(sizeof(DLTNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->val = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
有了结点创建函数,我们就可以开始对链表进行初始化了。
DLTNode* DLTInit()
{DLTNode* phead = BuyListNode(-1);phead->next = phead;phead->prev = phead;return phead;
}
这里要注意,因为我们的链表是循环的,所以链表最后是不指向空的,而是要循环回哨兵卫的头结点! 我们通过调试,也可以很清楚的看到他们的地址都是相同的。
3 链表打印
void DLTPrint(DLTNode* phead)
{assert(phead);DLTNode* cur = phead->next;while (cur != phead){printf("%d ", cur->val);cur = cur->next;}printf("\n");
}
打印链表非常简单,唯一需要注意的是,遍历结束的标志就是遍历的指针回到了哨兵卫的头结点!
4 链表的增删查改
4.1 链表尾插
void DLTPushBack(DLTNode* phead, DLTDataType x)
{//因为带哨兵卫的头结点,链表不可能为空,若为空就出错了assert(phead);DLTNode* newnode = BuyListNode(x);//找到尾结点DLTNode* next = phead->prev;newnode->next = phead;phead->prev = newnode;next->next = newnode;newnode->prev = next;
}
在开始对链表进行增删查改的时候,双向带头循环链表的优势就开始体现出来了,就像这次的尾插,我们不需要一个结点一个结点的遍历过去,通过哨兵卫的上一个结点就能马上找到尾结点,然后在尾结点和哨兵卫之间直接插入就完事了!
4.2 链表尾删
void DLTPopBack(DLTNode* phead)
{assert(phead);//哨兵卫头结点不能删除assert(phead->next != phead);//找到尾结点和尾结点的前一个结点DLTNode* next = phead->prev;DLTNode* nextPrev = next->prev;phead->prev = nextPrev;nextPrev->next = phead;free(next);
}
尾删也变得更加方便,依旧通过哨兵卫的上一个结点就能快速找到尾结点。
4.3 链表头插
void DLTPushFront(DLTNode* phead, DLTDataType x)
{assert(phead);DLTNode* newnode = BuyListNode(x);DLTNode* cur = phead->next;newnode->next = cur;cur->prev = newnode;phead->next = newnode;newnode->prev = phead;
}
双向带头循环链表的头插相比于单链表的头插也更为方便,由于哨兵卫的存在,我们只是改变的是结构体的内部,但是在单链表中却是要改变链表的头,就需要传二级指针或者需要改变返回值。
4.4 链表头删
void DLTPopFront(DLTNode* phead)
{assert(phead);assert(phead->next != phead);DLTNode* first = phead->next;DLTNode* second = first->next;phead->next = second;second->prev = phead;free(first);
}
4.4 查找链表中的值
DLTNode* DLTFind(DLTNode* phead, DLTDataType x)
{assert(phead);DLTNode* cur = phead->next;while (cur != phead){if (cur->val == x){return cur;}cur = cur->next;}return NULL;
}
4.5 在对应位置前面插入
void DLTInsert(DLTNode* pos, DLTDataType x)
{assert(pos);DLTNode* newnode = BuyListNode(x);DLTNode* posPrev = pos->prev;newnode->next = pos;pos->prev = newnode;posPrev->next = newnode;newnode->prev = posPrev;
}
在单链表中,我们要在pos位置前插入结点,那必须遍历找到pos位置的前一个结点,而且还要重点考虑会不会是头插这种情况。在这里,我们就完全不需要考虑这么多,通过prev指针,可以很轻松的实现这个功能。
4.6 删除对应位置的结点
void DLTErase(DLTNode* pos)
{assert(pos);DLTNode* posPrev = pos->prev;DLTNode* posNext = pos->next;posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}
5 链表销毁
void DLTDestroy(DLTNode* phead)
{assert(phead);DLTNode* cur = phead->next;while (cur != phead){DLTNode* next = cur->next;free(cur);cur = next;}free(phead);
}
对于链表的销毁,我们依旧是要一步步来,要注意链表和顺序表不同,是不能直接free的!
6 完整代码及运行结果
//DList.h#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>typedef int DLTDataType;typedef struct DListNode
{struct DListNode* next;struct DListNode* prev;DLTDataType val;
}DLTNode;DLTNode* BuyListNode(DLTDataType x);
//初始化
DLTNode* DLTInit();//打印
void DLTPrint(DLTNode* phead);//尾插
void DLTPushBack(DLTNode* phead, DLTDataType x);
//尾删
void DLTPopBack(DLTNode* phead);//头插
void DLTPushFront(DLTNode* phead, DLTDataType x);
//头删
void DLTPopFront(DLTNode* phead);//查找
DLTNode* DLTFind(DLTNode* phead, DLTDataType x);//在pos位置前插入
void DLTInsert(DLTNode* pos, DLTDataType x);//删除pos位置的结点
void DLTErase(DLTNode* pos);//链表销毁
void DLTDestroy(DLTNode* phead);
//DList.c#define _CRT_SECURE_NO_WARNINGS 1
#include"DList.h"DLTNode* BuyListNode(DLTDataType x)
{DLTNode* newnode = (DLTNode*)malloc(sizeof(DLTNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->val = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}//初始化
DLTNode* DLTInit()
{DLTNode* phead = BuyListNode(-1);phead->next = phead;phead->prev = phead;return phead;
}//打印
void DLTPrint(DLTNode* phead)
{assert(phead);DLTNode* cur = phead->next;while (cur != phead){printf("%d ", cur->val);cur = cur->next;}printf("\n");
}//尾插
void DLTPushBack(DLTNode* phead, DLTDataType x)
{//因为带哨兵卫的头结点,链表不可能为空,若为空就出错了assert(phead);DLTNode* newnode = BuyListNode(x);//找到尾结点DLTNode* next = phead->prev;newnode->next = phead;phead->prev = newnode;next->next = newnode;newnode->prev = next;
}//尾删
void DLTPopBack(DLTNode* phead)
{assert(phead);//哨兵卫头结点不能删除assert(phead->next != phead);//找到尾结点和尾结点的前一个结点DLTNode* next = phead->prev;DLTNode* nextPrev = next->prev;phead->prev = nextPrev;nextPrev->next = phead;free(next);
}//头插
void DLTPushFront(DLTNode* phead, DLTDataType x)
{assert(phead);DLTNode* newnode = BuyListNode(x);DLTNode* cur = phead->next;newnode->next = cur;cur->prev = newnode;phead->next = newnode;newnode->prev = phead;//DLTInsert(phead->next, x); //复用
}
//头删
void DLTPopFront(DLTNode* phead)
{assert(phead);assert(phead->next != phead);DLTNode* first = phead->next;DLTNode* second = first->next;phead->next = second;second->prev = phead;free(first);
}//查找
DLTNode* DLTFind(DLTNode* phead, DLTDataType x)
{assert(phead);DLTNode* cur = phead->next;while (cur != phead){if (cur->val == x){return cur;}cur = cur->next;}return NULL;
}//在pos位置前插入-- 注意pos位置不能是哨兵卫的头结点 C++中会改善这一点
void DLTInsert(DLTNode* pos, DLTDataType x)
{assert(pos);DLTNode* newnode = BuyListNode(x);DLTNode* posPrev = pos->prev;newnode->next = pos;pos->prev = newnode;posPrev->next = newnode;newnode->prev = posPrev;
}//删除pos位置的结点-- 注意pos位置不能是哨兵卫的头结点 C++中会改善这一点
void DLTErase(DLTNode* pos)
{assert(pos);DLTNode* posPrev = pos->prev;DLTNode* posNext = pos->next;posPrev->next = posNext;posNext->prev = posPrev;free(pos);
}//链表销毁
void DLTDestroy(DLTNode* phead)
{assert(phead);DLTNode* cur = phead->next;while (cur != phead){DLTNode* next = cur->next;free(cur);cur = next;}free(phead);
}
数据结构-带头双向循环链表(增删查改详解)相关推荐
- 数据结构篇 --- 带头双向循环链表增删查改
简述:有了前篇单链表的增删查改的基础 也是出于带头双向循环链表本身的优势 双链表的增删就变得格外简单.让我们一起来体验一下. 目录 带头双向循环链表图解: 带头双向循环链表基本结构: 带头双向循环链 ...
- 初阶数据结构之带头+双向+循环链表增删查实现(三)
文章目录 @[TOC](文章目录) 前言 一.带头双向循环链表的初始化 1.1带头双向循环链表的结构体定义 1.2初始化代码的实现 二.带头+双向+循环链表的增功能实现 2.1头插代码的实现 2.2尾 ...
- c语言单向循环链表实现增删,C语言单向非循环链表增删查改实现
SList.h #ifndef _SLIST_H_ #define _SLIST_H_ #include#include#include// 1.无头单向非循环链表增删查改实现 typedef int ...
- 【JAVA数据结构】链表的增删查改(单向不带头非循环链表)
月色与雪色之间,你是第三种绝色 大家好,这里是新一,请多关照
- 【JAVA数据结构】双向链表的增删查改
春水初生,春林初盛,春风十里,不如你 大家好,这里是新一,请多关照
- 【数据结构】链表:带头双向循环链表的增删查改
本篇要分享的内容是带头双向链表,以下为本片目录 目录 一.链表的所有结构 二.带头双向链表 2.1尾部插入 2.2哨兵位的初始化 2.3头部插入 2.4 打印链表 2.5尾部删除 2.6头部删除 2. ...
- 【数据结构】-关于带头双向循环链表的增删查改
作者:低调 作者宣言:写好每一篇博客 文章目录 前言 一.带头双向循环链表的实现 1.1创建返回链表的头结点 1.2开辟一个新的结点 1.3双向链表的销毁 1.4双向链表的打印 1.5双向链表尾插 1 ...
- 【数据结构初阶】链表(下)——带头双向循环链表的实现
目录 带头双向循环链表的实现 1.带头双向循环链表的节点类型 2.创建带头双向循环链表的节点 3.向带头双向循环链表中插入数据 <3.1>从链表尾部插入数据 <3.2>从链表头 ...
- C语言实现链表【二】带头双向循环链表
带头双向循环链表 结构描述: 带头双向循环链表:结构最复杂,一般用在单独存储数据.实际中使用的链表数据结构,都是带头双向循环链表.另外这个结构虽然结构复杂,但是使用代码实现以店会发现结构会带来很多优势 ...
最新文章
- SAP QM QPV3查看Sample-Drawing Procedure
- Go协程池设计思路(Task-Job-Worker)
- sqlserver 2014 删除主键约束
- debian jessie install note
- html画特殊图形(待修改)
- 2021高考成绩查询大连,2021年大连高考各高中成绩及本科升学率数据排名及分析...
- Redis高可用sentinel
- WMS仓储系统在管理中产生的盈利
- Android 跳转权限设置界面的终极方案
- 移动端下拉刷新,向后台请求数据
- 如何使用QQ快速截屏
- 给Eclipse设置android的SDK位置时,出现这个:This Android SDK requires Andr...ate ADT to the latest
- HTK搭建大词汇量连续语音识别系统(三)
- 项目质量管理可能存在的问题及应对策略
- 【数据分析框架】AARRR模型的数据指标体系AARRR模型AARRR数据指标总结
- 什么是SaaS系统,SaaS系统有哪些优势
- 宾得的宾干微距镜头DA35mm
- Netty 中的粘包和拆包详解
- 27岁自学Python转行靠谱吗?入行晚吗?
- 小米5查看设备号信息及验证type-c数据线