当前位置: 首页 > news >正文

【数据结构】带哨兵位双向循环链表

一、链表的分类

01 链表的分类

① 单向或双向

② 带头结点或不带头结点

③ 循环或非循环

02 常用的链表

根据上面的分类我们可以细分出8种不同类型的链表,这么多链表我们一个个讲解这并没有意义。我们实际中最常用的链表是 "无头单向非循环链表" 和 "带头双向循环链表" ,至于 "无头单项非循环链表" 我们在上一章已经讲述过了,我们下面将讲解 "带头双向循环列表" 

解读:

① 无头单向非循环链表:结构简单,一般不会单独用来存储数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等。

② 带头双向循环链表:结构最复杂,但是实现反而简单。一般用来单独存储数据,实际中使用的链表数据结构都是带头双向链表。另外,这个结构虽然结构复杂,但是使用代码实现后会发现结构会带来很多优势。双向链表严格来说只需要快速的实现两个接口,insert 和 earse 头尾的插入和删除就可以搞定了,这就是结构的优势。

二、双向循环链表的定义

01 定义双向循环链表

typedef int DListNodeDataType;typedef struct DoubleListNode {DListNodeDataType data;struct DoubleListNode* prev;	// 前驱结点指针struct DoubleListNode* next;	// 后继结点指针
}DListNode;

02 接口函数

// 初始化
DListNode* DListInit();
// 创建新结点
DListNode* CreateNewNode(DListNodeDataType x);
// 打印
void DListPrint(DListNode* pHead);
// 尾插
void DListPushBack(DListNode* pHead, DListNodeDataType x);
// 头插
void DListPushFront(DListNode* pHead, DListNodeDataType x);
// 尾删
void DListPopBack(DListNode* pHead);
// 头删
void DListPopFront(DListNode* pHead);
// 查找
DListNode* DListFind(DListNode* pHead, DListNodeDataType x);
// 在 pos 位置之前插入
void DListInsert(DListNode* pos, DListNodeDataType x);
// 删除 pos 位置的结点
void DListEarse(DListNode* pos);

三、接口函数的实现

01 初始化(DListInit)

DList.h:

// 初始化
DListNode* DListInit();

DList.c:

/* 初始化 */
DListNode* DListInit() {// 哨兵位头结点DListNode* pHead = (DListNode*)malloc(sizeof(DListNode));pHead->next = pHead;pHead->prev = pHead;return pHead;
}

02 打印(DListPrint)

DList.h:

// 打印
void DListPrint(DListNode* pHead);

DList.c:

/* 打印 */
void DListPrint(DListNode* pHead) {assert(pHead != NULL);DListNode* cur = pHead->next;while (cur != pHead) {printf("%d ", cur->data);cur = cur->next;}printf("\n");
}

03 创建新结点(CreateNewNode)

创建新节点要经常用,为了方便复用,根据经验我们先把 CreateNewNode 写好:

/* 创建新结点 */
DListNode* CreateNewNode(DListNodeDataType x) {DListNode* new_node = (DListNode*)malloc(sizeof(DListNode));if (new_node == NULL) {printf("malloc failed");exit(-1);}new_node->data = x;new_node->next = NULL;new_node->prev = NULL;return new_node;
}

04 尾插(DListPushBack)

DList.h:

// 尾插
void DListPushBack(DListNode* pHead, DListNodeDataType x);

因为不用改变 pList,所以不需要使用二级指针。

DList.c:

/* 尾插 */
void DListPushBack(DListNode* pHead, DListNodeDataType x)
{assert(pHead != NULL);DListNode* tail = pHead->prev;DListNode* new_node = CreateNewNode(x);// pHead  tail  new_nodetail->next = new_node;new_node->prev = tail;pHead->prev = new_node;new_node->next = pHead;
}

 解读:

① 首先防止 pHead 为空。

② 因为要实现尾插,我们要找出尾部节点。我们这里并不需要像之前学单链表时需要创建寻尾指针找到尾部节点,直接用 pHead->prev 就可以了。是不是非常的方便?随后创建新节点,直接调用我们刚才写的  CreateNewNode 接口即可。

③ 实现尾插操作,画出来可以更好地写出代码。在注释里写一个简单地思路草图也是可以的,只要画好他们之间的链接关系,再写代码会变得非常简单:

思路草图: pHead   tail   new_node(尾插目标) 

然后再根据尾插的思路,我们就可以轻松写出代码了。

Test.c:

#include "DList.h"void TestList1() 
{DListNode* pList = DListInit();DListPushBack(pList, 1);DListPushBack(pList, 2);DListPushBack(pList, 3);DListPushBack(pList, 4);DListPrint(pList);
}int main() 
{TestList1();return 0;
}

运行结果如下:

05 头插(DListPushFront)

DList.h:

// 头插
void DListPushFront(DListNode* pHead, DListNodeDataType x);

解读:双向链表头插,即在第一个数据前面,也就是哨兵位和第一个数据之间插入一个数据。

DList.c:

/* 头插 */
void DListPushFront(DListNode* pHead, DListNodeDataType x)
{assert(pHead != NULL);DListNode* new_node = CreateNewNode(x);DListNode* pHeadNext = pHead->next;// pHead new_node pHeadNextpHead->next = new_node;new_node->next = pHeadNext;new_node->prev = pHead;pHeadNext->prev = new_node;
}

解读:

① 首先防止 pHead 为空。

② 既然是插入,那就创建新节点。双向链表头插,我们要找到第一个节点,通过 pHead->next 就可以拿到了,我们将它取名为 pHeadNext(表示第一个节点)。在哨兵位和第一个数据之间插入数据,画出草图就是:

思路草图: pHead  new_node(头插目标)  pHeadnext

根据草图我们开始写代码,将他们互相链接起来即可!

Test.c:

void TestList2()
{DListNode* pList = DListInit();DListPushFront(pList, 1);DListPushFront(pList, 2);DListPushFront(pList, 3);DListPushFront(pList, 4);DListPrint(pList);
}

运行结果如下:

06 尾删(DListPopBack)

DList.h:

// 尾删
void DListPopBack(DListNode* pHead);

DList.c:

/* 尾删 */
void DListPopBack(DListNode* pHead)
{assert(pHead != NULL);assert(pHead->next != pHead);// pHead tailPrev tailDListNode* tail = pHead->prev;DListNode* tailPrev = tail->prev;free(tail);tailPrev->next = pHead;pHead->prev = tailPrev;
}

解读:

① 首先防止 pHead 为空,这里还要预防下哨兵位被不小心干掉的情况,哨兵位指向自己就说明链表为空,链表为空就不能再继续删除了,断言 pHead->next == pHead 就可以了。

② 因为是尾删,我们要把尾部指针和它的前驱指针标出来。我们来画一下思路草图:

思路草图: pHead   tailPrev  tail(待删目标)

另外,这里使用前驱指针是为了避免 free 掉之后就没法链接了。

我们有了 tailPrev 了,所以我们先 free 掉 tail 也关系,此时 tail 已经被释放掉了,再进行链接就可以了。

Test.c:

void TestList3()
{DListNode* pList = DListInit();DListPushFront(pList, 1);DListPushFront(pList, 2);DListPushFront(pList, 3);DListPushFront(pList, 4);DListPrint(pList);DListPopBack(pList);DListPrint(pList);
}

运行结果如下:

07 头删(DListPopFront)

DList.h:

// 头删
void DListPopFront(DListNode* pHead);

DList.c:

/* 头删 */
void DListPopFront(DListNode* pHead)
{assert(pHead != NULL);assert(pHead->next != pHead);// pHead pHeadNext pHeadNextNextDListNode* pHeadNext = pHead->next;DListNode* pHeadNextNext = pHeadNext->next;pHead->next = pHeadNextNext;pHeadNextNext->prev = pHead;free(pHeadNext);
}

Test.c:

void TestList4()
{DListNode* pList = DListInit();DListPushFront(pList, 1);DListPushFront(pList, 2);DListPushFront(pList, 3);DListPushFront(pList, 4);DListPrint(pList);DListPopFront(pList);DListPrint(pList);
}

运行结果如下:

08 查找(DListFind)

DList.h:

// 查找
DListNode* DListFind(DListNode* pHead, DListNodeDataType x);

DList.c:

/* 查找 */
DListNode* DListFind(DListNode* pHead, DListNodeDataType x)
{assert(pHead);DListNode* cur = pHead->next;while (cur != pHead) {if (cur->data == x)return cur;cur = cur->next;}return NULL;
}

解读:很简单,和打印的思路一样。只需要创建一个 cur 从第一个有效节点(pHead->next)开始遍历链表就可以了。如果 cur->data 和传入的 x,即需要查找的值相同就返回 curcur 是带有值和地址的。如果整个链表都走完了还没有找到相同的值,就返回 NULL 。

09 在 pos 位置之前插入(DListInsert)

DList.h:

// 在 pos 位置之前插入
void DListInsert(DListNode* pos, DListNodeDataType x);

DList.c:

/* 在 pos 位置之前插入 */
void DListInsert(DListNode* pos, DListNodeDataType x)
{assert(pos != NULL);DListNode* new_node = CreateNewNode(x);DListNode* posPrev = pos->prev;// posPrev new_node posposPrev->next = new_node;new_node->prev = posPrev;new_node->next = pos;pos->prev = new_node;
}

解读:

① 防止 pos 传入为空。

② 首先创建新节点。因为是在 pos 位置之前插入,为了方便,我们先标出 pos 的前驱指针 posPrev,随后我们画出草图:

思路草图:posPrev  newnode(待插目标)   pos

此时,DListInsert 就大功告成了!它就是双向链表最核心的两个接口之一,我们可以将这个万能的 "pos位置之前插入" 复用到我们刚才写的尾插和头插,即 DListPushBack 和 DListPushFront 中:

/* 尾插 */
void DListPushBack(DListNode* pHead, DListNodeDataType x)
{assert(pHead != NULL);//DListNode* tail = pHead->prev;//DListNode* new_node = CreateNewNode(x);//// pHead  tail  new_node//tail->next = new_node;//new_node->prev = tail;//pHead->prev = new_node;//new_node->next = pHead;DListInsert(pHead, x);
}
/* 头插 */
void DListPushFront(DListNode* pHead, DListNodeDataType x)
{assert(pHead != NULL);//DListNode* new_node = CreateNewNode(x);//DListNode* pHeadNext = pHead->next;//// pHead new_node pHeadNext//pHead->next = new_node;//new_node->next = pHeadNext;//new_node->prev = pHead;//pHeadNext->prev = new_node;DListInsert(pHead->next, x);
}

10 删除 pos 位置的结点(DListErase)

DList.h:

// 删除 pos 位置的结点
void DListEarse(DListNode* pos);

DList.c:

/* 删除 pos 位置的结点 */
void DListErase(DListNode* pos)
{assert(pos != NULL); DListNode* posPrev = pos->prev; DListNode* posNext = pos->next; // posPrev  pos  posNextposPrev->next = posNext;posNext->prev = posPrev;free(pos);pos = NULL;
}

解读:

① 防止 pos 传入为空。

② 既然要删除 pos 位置的节点,我们先标出 pos 的前驱指针 posPrev 和 pos 的后继指针 posNext ,随后我们画出草图:

思路草图: posPrev  pos  posNext

此时,DListErase 也搞定了!我们可以将这个万能的 "pos位置删除" 复用到之前写的尾删和头删那里,即 DListPopBack 和 DListPopFront 中:

/* 尾删 */
void DListPopBack(DListNode* pHead)
{assert(pHead != NULL);assert(pHead->next != pHead);//// pHead tailPrev tail//DListNode* tail = pHead->prev;//DListNode* tailPrev = tail->prev;//free(tail);//tailPrev->next = pHead;//pHead->prev = tailPrev;DListErase(pHead->prev);
}
/* 头删 */
void DListPopFront(DListNode* pHead)
{assert(pHead != NULL);assert(pHead->next != pHead);//// pHead pHeadNext pHeadNextNext//DListNode* pHeadNext = pHead->next;//DListNode* pHeadNextNext = pHeadNext->next;//pHead->next = pHeadNextNext;//pHeadNextNext->prev = pHead;//free(pHeadNext);DListErase(pHead->next);
}

四、完整代码

DList.h:

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int DListNodeDataType;typedef struct DoubleListNode {DListNodeDataType data;struct DoubleListNode* prev;	// 前驱结点指针struct DoubleListNode* next;	// 后继结点指针
}DListNode;// 初始化
DListNode* DListInit();
// 创建新结点
DListNode* CreateNewNode(DListNodeDataType x);
// 打印
void DListPrint(DListNode* pHead);
// 尾插
void DListPushBack(DListNode* pHead, DListNodeDataType x);
// 头插
void DListPushFront(DListNode* pHead, DListNodeDataType x);
// 尾删
void DListPopBack(DListNode* pHead);
// 头删
void DListPopFront(DListNode* pHead);
// 查找
DListNode* DListFind(DListNode* pHead, DListNodeDataType x);
// 在 pos 位置之前插入
void DListInsert(DListNode* pos, DListNodeDataType x);
// 删除 pos 位置的结点
void DListErase(DListNode* pos);

DList.c:

#include "DList.h"/* 初始化 */
DListNode* DListInit() {// 哨兵位头结点DListNode* pHead = (DListNode*)malloc(sizeof(DListNode));pHead->next = pHead;pHead->prev = pHead;return pHead;
}/* 打印 */
void DListPrint(DListNode* pHead) {assert(pHead != NULL);DListNode* cur = pHead->next;while (cur != pHead) {printf("%d ", cur->data);cur = cur->next;}printf("\n");
}/* 创建新结点 */
DListNode* CreateNewNode(DListNodeDataType x) {DListNode* new_node = (DListNode*)malloc(sizeof(DListNode));if (new_node == NULL) {printf("malloc failed");exit(-1);}new_node->data = x;new_node->next = NULL;new_node->prev = NULL;return new_node;
}/* 尾插 */
void DListPushBack(DListNode* pHead, DListNodeDataType x)
{assert(pHead != NULL);//DListNode* tail = pHead->prev;//DListNode* new_node = CreateNewNode(x);//// pHead  tail  new_node//tail->next = new_node;//new_node->prev = tail;//pHead->prev = new_node;//new_node->next = pHead;DListInsert(pHead, x);
}/* 头插 */
void DListPushFront(DListNode* pHead, DListNodeDataType x)
{assert(pHead != NULL);//DListNode* new_node = CreateNewNode(x);//DListNode* pHeadNext = pHead->next;//// pHead new_node pHeadNext//pHead->next = new_node;//new_node->next = pHeadNext;//new_node->prev = pHead;//pHeadNext->prev = new_node;DListInsert(pHead->next, x);
}/* 尾删 */
void DListPopBack(DListNode* pHead)
{assert(pHead != NULL);assert(pHead->next != pHead);//// pHead tailPrev tail//DListNode* tail = pHead->prev;//DListNode* tailPrev = tail->prev;//free(tail);//tailPrev->next = pHead;//pHead->prev = tailPrev;DListErase(pHead->prev);
}/* 头删 */
void DListPopFront(DListNode* pHead)
{assert(pHead != NULL);assert(pHead->next != pHead);//// pHead pHeadNext pHeadNextNext//DListNode* pHeadNext = pHead->next;//DListNode* pHeadNextNext = pHeadNext->next;//pHead->next = pHeadNextNext;//pHeadNextNext->prev = pHead;//free(pHeadNext);DListErase(pHead->next);
}/* 查找 */
DListNode* DListFind(DListNode* pHead, DListNodeDataType x)
{assert(pHead);DListNode* cur = pHead->next;while (cur != pHead) {if (cur->data == x)return cur;cur = cur->next;}return NULL;
}/* 在 pos 位置之前插入 */
void DListInsert(DListNode* pos, DListNodeDataType x)
{assert(pos != NULL);DListNode* new_node = CreateNewNode(x);DListNode* posPrev = pos->prev;// posPrev new_node posposPrev->next = new_node;new_node->prev = posPrev;new_node->next = pos;pos->prev = new_node;
}/* 删除 pos 位置的结点 */
void DListErase(DListNode* pos)
{assert(pos != NULL); DListNode* posPrev = pos->prev; DListNode* posNext = pos->next; // posPrev  pos  posNextposPrev->next = posNext;posNext->prev = posPrev;free(pos);pos = NULL;
}

文章转载自:

http://a80KMH09.xxjgz.cn
http://GAqm7SlF.xxjgz.cn
http://v9GPtsnN.xxjgz.cn
http://Rjtf04JH.xxjgz.cn
http://jMHeV2OY.xxjgz.cn
http://gWOSZhl0.xxjgz.cn
http://Rz9uw6CY.xxjgz.cn
http://mus9GxoC.xxjgz.cn
http://kyRjCYdj.xxjgz.cn
http://QhV7p2ne.xxjgz.cn
http://pLxiSJSN.xxjgz.cn
http://wZFd4sSs.xxjgz.cn
http://a2YNa66H.xxjgz.cn
http://Tvs2j5K7.xxjgz.cn
http://1A08Mb70.xxjgz.cn
http://KwveOYEf.xxjgz.cn
http://KXoNgpY2.xxjgz.cn
http://jjU8bcEc.xxjgz.cn
http://mMmoHRQe.xxjgz.cn
http://gOw8IXkr.xxjgz.cn
http://kZ8fvP8R.xxjgz.cn
http://h6P2zT1P.xxjgz.cn
http://HevjfSiQ.xxjgz.cn
http://r0odX3WR.xxjgz.cn
http://uyZR646O.xxjgz.cn
http://9vOIZmYO.xxjgz.cn
http://Mbi8noow.xxjgz.cn
http://QV1IwJyH.xxjgz.cn
http://nYJMAd4V.xxjgz.cn
http://0BfCzNaC.xxjgz.cn
http://www.dtcms.com/a/369900.html

相关文章:

  • Python基础之封装单继承
  • [数据结构] ArrayList(顺序表)与LinkedList(链表)
  • Fantasia3D:高质量文本到3D内容创建工具
  • Elasticsearch面试精讲 Day 10:搜索建议与自动补全
  • 【3D算法技术】blender中,在曲面上如何进行贴图?
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘mkdocs’问题
  • 【牛客刷题-剑指Offer】BM18 二维数组中的查找:一题四解,从暴力到最优
  • 推荐的Java服务环境:JDK17+ZGC(JDK 21的ZGC支持分代回收,性能更高)
  • [光学原理与应用-431]:非线性光学 - 能生成或改变激光波长的物质或元件有哪些?
  • 心路历程- Linux用户组的整理
  • 前端登录鉴权详解
  • CodeSandbox Desktop:零配置项目启动工具,实现项目环境隔离与Github无缝同步
  • Lua > Mac Mini M4安装openresty
  • SpringBootWeb 篇-深入了解 ThreadLocal 存在内存泄漏问题
  • Django 项目6:表单与认证系统
  • 【架构艺术】通过标准化事件解决变更检测能力的调度问题
  • Eureka与Nacos的区别-服务注册+配置管理
  • Python毕业设计推荐:基于Django的饮食计划推荐与交流分享平台 饮食健康系统 健康食谱计划系统
  • 基于 Gemini 的 CI/CD 自动化测评 API 集成实战教程
  • 基于定制开发开源AI智能名片S2B2C商城小程序的DMP平台离线文件上传功能优化研究
  • 友猫社区APP源码与小程序端部署详解
  • HJ65查找两个字符串a,b中的最长公共子串(medium,dp,进入题目后题目序号是HJ21)
  • 如何减少微型导轨表面破损情况?
  • Python中list()使用详解及注意事项
  • Patr1.第1-5章
  • Java线程通信
  • Linux匿名管道和命名管道以及共享内存
  • 【01背包问题变体】P1282 多米诺骨牌
  • 【C++】类和对象(三)
  • 【面试向】人工智能机器学习介绍