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

上图就是带头双向循环链表的主要结构了,很多同学看到这个结构就想知难而退了,但是只要耐心深究,就会发现除了结构比单链表复杂以外,它的实现反而简单了!!!

目录

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);
}

数据结构-带头双向循环链表(增删查改详解)相关推荐

  1. 数据结构篇 --- 带头双向循环链表增删查改

    简述:有了前篇单链表的增删查改的基础 也是出于带头双向循环链表本身的优势  双链表的增删就变得格外简单.让我们一起来体验一下. 目录 带头双向循环链表图解: 带头双向循环链表基本结构: 带头双向循环链 ...

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

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

  3. c语言单向循环链表实现增删,C语言单向非循环链表增删查改实现

    SList.h #ifndef _SLIST_H_ #define _SLIST_H_ #include#include#include// 1.无头单向非循环链表增删查改实现 typedef int ...

  4. 【JAVA数据结构】链表的增删查改(单向不带头非循环链表)

    月色与雪色之间,你是第三种绝色 大家好,这里是新一,请多关照

  5. 【JAVA数据结构】双向链表的增删查改

    春水初生,春林初盛,春风十里,不如你 大家好,这里是新一,请多关照

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

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

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

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

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

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

  9. C语言实现链表【二】带头双向循环链表

    带头双向循环链表 结构描述: 带头双向循环链表:结构最复杂,一般用在单独存储数据.实际中使用的链表数据结构,都是带头双向循环链表.另外这个结构虽然结构复杂,但是使用代码实现以店会发现结构会带来很多优势 ...

最新文章

  1. SAP QM QPV3查看Sample-Drawing Procedure
  2. Go协程池设计思路(Task-Job-Worker)
  3. sqlserver 2014 删除主键约束
  4. debian jessie install note
  5. html画特殊图形(待修改)
  6. 2021高考成绩查询大连,2021年大连高考各高中成绩及本科升学率数据排名及分析...
  7. Redis高可用sentinel
  8. WMS仓储系统在管理中产生的盈利
  9. Android 跳转权限设置界面的终极方案
  10. 移动端下拉刷新,向后台请求数据
  11. 如何使用QQ快速截屏
  12. 给Eclipse设置android的SDK位置时,出现这个:This Android SDK requires Andr...ate ADT to the latest
  13. HTK搭建大词汇量连续语音识别系统(三)
  14. 项目质量管理可能存在的问题及应对策略
  15. 【数据分析框架】AARRR模型的数据指标体系AARRR模型AARRR数据指标总结
  16. 什么是SaaS系统,SaaS系统有哪些优势
  17. 宾得的宾干微距镜头DA35mm
  18. Netty 中的粘包和拆包详解
  19. 27岁自学Python转行靠谱吗?入行晚吗?
  20. 小米5查看设备号信息及验证type-c数据线

热门文章

  1. 超全的C++开发工程师面经
  2. 欧拉如何解决哥尼斯堡七桥问题(一)
  3. Mac双屏时程序坞(任务栏)跑到副屏上怎么办
  4. 无法打开文件 “opencv_world400d.lib”
  5. 程序员和产品经理之间的恩怨情仇 1
  6. 2017年度计划中期自我反省
  7. sdutacm-团战可以输、提莫必须死
  8. 最伟大的操作系统sinox和它的开发者sjm100的故事
  9. 语音播报库AVFoundation
  10. SQL Server管理相关的注册表技巧