【数据结构】双向带头(哨兵位)循环链表 —详细讲解(赋源码)

欢迎来到我的Blog,点击关注哦💕

前面介绍单向不带头(哨兵位)链表,双向相比于单向而言,存贮,查找,会更加便利。

前言

双向循环列表是一种特殊的数据结构,它结合了双向链表和循环链表的特点。在双向循环列表中,每个节点除了拥有指向下一个节点的指针外,还拥有指向上一个节点的指针。此外,列表的头节点和尾节点通过这些指针相互连接,形成一个闭环。这种结构允许从任何一个节点开始,既可以向前遍历,也可以向后遍历,直到回到起点,从而实现高效的双向遍历。

一、双向带头循环链表的基本介绍

  • 定义节点结构:每个节点包含数据部分和两个指针部分,分别指向前一个节点和后一个节点。

  • 创建头节点:头节点是双向循环列表的起始点,它的前驱指针指向自己,后继指针也指向自己,形成一个闭环。

  • 插入节点:在特定位置插入新节点时,需要更新新旧节点的前驱和后继指针,确保列表的完整性。

  • 删除节点:删除特定节点时,同样需要更新前后节点的指针,避免出现悬空指针。

  • 遍历列表:可以通过前驱指针或后继指针从任意节点开始,向前或向后遍历整个列表。

双向带头链表的操作

常见操作包括初始化插入删除查找遍历。这些操作通常涉及到对链表节点的指针进行操作,以实现数据的动态管理。

双向带头循环链表基本理解

head
A
b
C
D
E
....

二、双向带头循环链表的实现

2.1 双向带头循环链表的功能

//链表初始化创建哨兵位
ListNode* ListInit();
//连接表增加节点
ListNode* BuyNewNode(ListDataType x);
//链表打印
void DlistPrint(ListNode* phead);
//链表尾插
void DLlistPushBack(ListNode* phead, ListDataType x);
//链表尾删
void DLlistPopBack(ListNode* phead);
//链表头插
void DLlistPushFront(ListNode* phead, ListDataType x);
//链表头删
void DLlistPopFront(ListNode* phead);
//链表查找
ListNode* DListFind(ListNode* phead, ListDataType pos);
//链表pos插入
void DListInset(ListNode* pos, ListDataType);
//链表pos删除
void DListErase(ListNode* phead, ListNode* pos);
//链表销毁
void DListDestory(ListNode* phead);

2.2 定义节点结构体

由于是双向带头,需要定义一个nextprev

typedef int ListDataType;typedef struct DList
{int data;struct Dlist* prev;struct Dlist* next;}ListNode;

2.3 链表的增加节点

这里可以相比较于单链表的操作,进行操作。

ListNode* BuyNewNode(ListDataType x)
{ListNode* newcode = (ListNode*)malloc(sizeof(ListNode));if (newcode == NULL){perror("malloc fail");return NULL;}newcode->data = x;newcode->next = NULL;newcode->prev = NULL;return newcode;}

2.4 链表的初始化

这个操作就是带头

//链表初始化创建哨兵卫
ListNode* ListInit()
{ListNode* head = BuyNewNode(-1);head->next = head;head->prev = head;return head;
}

2.5 链表的遍历

//链表打印
void DlistPrint(ListNode* phead)
{ListNode* cur = phead->next;printf("<-head->");while (cur != phead){printf("%d<->", cur->data);cur = cur->next;}printf("\n");
}

2.6 链表的尾插

由于双向链表结构的特殊性,相比于单链表的效率更加高。

操作就是哨兵位的上一个节点,进行插入。

//链表尾插
void DLlistPushBack(ListNode* phead, ListDataType x)
{assert(phead);ListNode* newcode = BuyNewNode(x);ListNode* tail = phead->prev;tail->next = newcode;newcode->prev = tail;newcode->next = phead;phead->prev = newcode;}

2,7 链表的尾删

单链表进行尾部删的时候会进行断言,判断链表是否为空,双向链表也是如此,但是进行的操作是哨兵位的上一个节点或者下一个节点是否指向自己。

判断是否为空链表

我们利用bool类型判断真或者假

bool ListEmpty(ListNode* phead)
{assert(phead);return phead->prev == phead;
}

尾删操作

//链表尾删
void DLlistPopBack(ListNode* phead)
{assert(phead);assert(!ListEmpty(phead));ListNode* tail = phead->prev;ListNode* tailprev = tail->prev;tailprev->next = phead;phead->prev = tailprev;free(tail);tail = NULL;
}

2.8 链表的头插

记录哨兵位下一个节点,进行头插入。

//链表头插
void DLlistPushFront(ListNode* phead, ListDataType x)
{assert(phead);ListNode* newcode = BuyNewNode(x);ListNode* cur = phead->next;phead->next = newcode;newcode->prev = phead;newcode->next = cur;cur->prev = newcode;
}

2.9 链表的头部删

头删的操作同,尾部删除一样,就是换了哥位置。(但是还是要注意进行判断链表为空的问题)

void DLlistPopFront(ListNode* phead)
{assert(phead);assert(!ListEmpty(phead));ListNode* cur = phead->next;ListNode* curnext = cur->next;phead->next = curnext;curnext->prev = phead;free(cur);cur = NULL;}

2.10 链表的查找

链表的查找的时候返回写成结构体指针的形式,以便于进行指定位置的插入和删除。

//链表查找
ListNode* DListFind(ListNode* phead, ListDataType pos)
{assert(phead);ListNode* cur = phead->next;while (cur != phead && cur->data != pos){if (cur->data == pos){break;}cur = cur->next;}return cur;
}

2.11 链表指定位置的插入

在这里面不运用二级指针的原因就是我们改变的是结构体的变量,并不是结构体指针。

//链表pos插入
void DListInset( ListNode* pos,ListDataType x)
{assert(pos);ListNode* newcode = BuyNewNode(x);ListNode* cur = pos->prev;cur->next = newcode;newcode->prev = cur;newcode->next = pos;pos->prev = newcode;}

2.12 链表指定位置的删除

//链表pos删除
void DListErase(ListNode* phead, ListNode* pos)
{assert(!ListEmpty(phead));assert(pos);ListNode* posprev = pos->prev;ListNode* posnext = pos->next;posprev->next = posnext;posnext->prev = posprev;free(pos);//pos = NULL;//由于一级指针,NULL改变不了形式参数}

2.13 链表的销毁

//链表销毁
void DListDestory(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){ListNode* curnext = cur->next;free(cur);//在循环第一句自动下一步cur = curnext;}//释放哨兵位头节点//不需要置空,形参数free(phead);
}

源码

DList.h

#pragma once#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>typedef int ListDataType;typedef struct DList
{int data;struct Dlist* prev;struct Dlist* next;}ListNode;//链表初始化创建哨兵卫
ListNode* ListInit();
//连接表增加节点
ListNode* BuyNewNode(ListDataType x);
//链表打印
void DlistPrint(ListNode* phead);
//链表尾插
void DLlistPushBack(ListNode* phead, ListDataType x);
//链表尾删
void DLlistPopBack(ListNode* phead);
//链表头插
void DLlistPushFront(ListNode* phead, ListDataType x);
//链表头删
void DLlistPopFront(ListNode* phead);
//链表查找
ListNode* DListFind(ListNode* phead, ListDataType pos);
//链表pos插入
void DListInset(ListNode* pos, ListDataType);
//链表pos删除
void DListErase(ListNode* phead, ListNode* pos);
//链表销毁
void DListDestory(ListNode* phead);

DList.c

#define  _CRT_SECURE_NO_WARNINGS   1#include "DList.h"ListNode* BuyNewNode(ListDataType x)
{ListNode* newcode = (ListNode*)malloc(sizeof(ListNode));if (newcode == NULL){perror("malloc fail");return NULL;}newcode->data = x;newcode->next = NULL;newcode->prev = NULL;return newcode;}//链表初始化创建哨兵卫
ListNode* ListInit()
{ListNode* head = BuyNewNode(-1);head->next = head;head->prev = head;return head;
}//链表打印
void DlistPrint(ListNode* phead)
{ListNode* cur = phead->next;printf("<-head->");while (cur != phead){printf("%d<->", cur->data);cur = cur->next;}printf("\n");
}//链表尾插
void DLlistPushBack(ListNode* phead, ListDataType x)
{assert(phead);ListNode* newcode = BuyNewNode(x);ListNode* tail = phead->prev;tail->next = newcode;newcode->prev = tail;newcode->next = phead;phead->prev = newcode;}//判断链表是不是空
bool ListEmpty(ListNode* phead)
{assert(phead);return phead->prev == phead;
}//链表尾删
void DLlistPopBack(ListNode* phead)
{assert(phead);assert(!ListEmpty(phead));ListNode* tail = phead->prev;ListNode* tailprev = tail->prev;tailprev->next = phead;phead->prev = tailprev;free(tail);tail = NULL;
}//链表头插
void DLlistPushFront(ListNode* phead, ListDataType x)
{assert(phead);ListNode* newcode = BuyNewNode(x);ListNode* cur = phead->next;phead->next = newcode;newcode->prev = phead;newcode->next = cur;cur->prev = newcode;
}//链表头删
void DLlistPopFront(ListNode* phead)
{assert(phead);assert(!ListEmpty(phead));ListNode* cur = phead->next;ListNode* curnext = cur->next;phead->next = curnext;curnext->prev = phead;free(cur);cur = NULL;}//链表查找
ListNode* DListFind(ListNode* phead, ListDataType pos)
{assert(phead);ListNode* cur = phead->next;while (cur != phead && cur->data != pos){if (cur->data == pos){break;}cur = cur->next;}return cur;
}//链表pos插入
void DListInset( ListNode* pos,ListDataType x)
{assert(pos);ListNode* newcode = BuyNewNode(x);ListNode* cur = pos->prev;cur->next = newcode;newcode->prev = cur;newcode->next = pos;pos->prev = newcode;}//链表pos删除
void DListErase(ListNode* phead, ListNode* pos)
{assert(!ListEmpty(phead));assert(pos);ListNode* posprev = pos->prev;ListNode* posnext = pos->next;posprev->next = posnext;posnext->prev = posprev;free(pos);//由于一级指针,NULL改变不了形式参数}//链表销毁
void DListDestory(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){ListNode* curnext = cur->next;free(cur);//在循环第一句自动下一步cur = curnext;}//释放哨兵位头节点//不需要值空,形参数free(phead);
}

test.c

#define  _CRT_SECURE_NO_WARNINGS   1#include "DList.h"void testDlist1()
{ListNode* plist = ListInit();DLlistPushBack(plist, 1);DLlistPushBack(plist, 2);DLlistPushBack(plist, 3);DLlistPushBack(plist, 4);ListNode* ret = DListFind(plist, 2); //ret->data = (ret->data) * 2;DListInset(ret, 40);DListErase(plist, ret);DLlistPopBack(plist);DlistPrint(plist);}
void testDlist2()
{ListNode* plist = ListInit();DLlistPushFront(plist, 1);DLlistPushFront(plist, 2);DLlistPushFront(plist, 3);DLlistPushFront(plist, 4);DLlistPushFront(plist, 5);DLlistPopFront(plist);DLlistPopFront(plist);DlistPrint(plist);}int main()
{testDlist1();//testDlist2();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/22304.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL 存储过程(一)

本篇主要介绍MySQL存储过程的相关内容 目录 一、什么是存储过程&#xff1f; 二、基本语法 创建存储过程 调用存储过程 查看存储过程 删除存储过程 三、变量 系统变量 用户自定义变量 局部变量 四、存储过程的参数 in out inout 一、什么是存储过程&#xff1f…

摄像头对人脸进行性别和年龄的判断

摄像头对人脸性别和年龄判断 导入必要的库加载预训练的人脸检测模型加载预训练的性别和年龄识别模型定义性别和年龄的标签列表打开摄像头从摄像头读取一帧转换为灰度图像检测人脸遍历检测到的人脸显示视频流按 ‘q’ 或点击窗口的“”退出循环释放摄像头和销毁所有窗口全部代码…

python中的while循环

没有循环时&#xff0c;想打印0-100之间的数字&#xff0c;则需要循环多次&#xff0c;例&#xff1a; print(0) print(1) print(2) print(3) ... print(99) 但是使用循环的话&#xff0c;就不会有那么麻烦 while 循环 while 这个单词有“在……时”的含义&#xff0c;whil…

【UnityShader入门精要学习笔记】第十七章 表面着色器

本系列为作者学习UnityShader入门精要而作的笔记&#xff0c;内容将包括&#xff1a; 书本中句子照抄 个人批注项目源码一堆新手会犯的错误潜在的太监断更&#xff0c;有始无终 我的GitHub仓库 总之适用于同样开始学习Shader的同学们进行有取舍的参考。 文章目录 表面着色器…

2024年手机能做的赚钱软件有哪些?整理了八个手机能做的正规赚钱软件分享

在这个指尖滑动的时代&#xff0c;手机不仅仅是通讯工具&#xff0c;更是我们探索财富的钥匙。你是否曾幻想过&#xff0c;躺在沙发上&#xff0c;轻轻一滑&#xff0c;就能让钱包鼓起来&#xff1f; 今天&#xff0c;就让我们一起来探索那些隐藏在手机里的赚钱秘笈&#xff0c…

使用 Vue 3 和 vue-print-nb 插件实现复杂申请表的打印

文章目录 1&#xff1a;创建 Vue 3 项目2&#xff1a;安装 vue-print-nb 插件3&#xff1a;配置 vue-print-nb 插件4&#xff1a;创建一个复杂的申请表5&#xff1a;使用 ApplicationForm 组件6&#xff1a;运行项目 在开发管理系统或申请表打印功能时&#xff0c;打印功能是一…

光伏无人机踏勘需要使用哪些设备?用到哪些原理?

随着全球能源结构的转型和绿色能源的大力推广&#xff0c;光伏电站的建设和运维正成为能源领域的热点。然而&#xff0c;光伏电站的选址、建设和后期运维过程中&#xff0c;往往面临着地形复杂、设备分散、巡检难度大等挑战。在这一背景下&#xff0c;无人机踏勘技术以其独特的…

qt程序打包成一个exe

首先在release模式下编译然后用windeployqt打包 具体步骤参照我这篇文章&#xff1a; https://blog.csdn.net/weixin_73548574/article/details/134932044 然后使用一个加壳工具&#xff1a;https://enigmaprotector.com/en/downloads.html 下载安装后打开 到此完成&#…

c++题目_P1546 [USACO3.1] 最短网络 Agri-Net

题目背景 Farmer John 被选为他们镇的镇长&#xff01;他其中一个竞选承诺就是在镇上建立起互联网&#xff0c;并连接到所有的农场。当然&#xff0c;他需要你的帮助。 题目描述 FJ 已经给他的农场安排了一条高速的网络线路&#xff0c;他想把这条线路共享给其他农场。为了用…

数仓建模—指标拆解和选取

数仓建模—指标拆解和选取 第一节指标体系初识介绍了什么是指标体系 第二节指标体系分类分级和评价管理介绍了指标体系管理相关的,也就是指标体系的分级分类 这一节我们看一下指标体系的拆解和指标选取,这里我们先说指标选取,其实在整个企业的数字化建设过程中我们其实最…

进程和任务管理器

一、查看和控制进程 1.1ps命令 &#xff08;1&#xff09;ps 命令——查看静态的进程统计信息&#xff08;Processes Statistic&#xff09; PID TTY TIME CMD 1579 pts/1 00:00:00 bash 1730 pts/1 00:00:00 ps PID&#xff1a;进程IDTTY &#xff08;进程id&#xff0…

微信小程序发布遇到的一些问题记录

1.报错组件没有按需导入 在该路径配置微信小程序添加"lazyCodeLoading" : "requiredComponents" "mp-weixin" : { "appid" : "你的appid", "setting" : { "urlCheck" : f…

VS_图片转换点云

文章内容: 通过OpenCV读取图片数据将图片数据转换为点云显示点云保存点云到文件图片转换灰度图显示灰度图文章介绍 代码是用Ai工具生成后在VS上运行没有问题的。 可以参考里面读写PCL文件,PCL的显示等内容。 #include <opencv2/opencv.hpp> #include <pcl/io/pcd_…

ElementUI中date-picker组件,怎么给选择一个月份范围中大写月份改为阿拉伯数组月份(例如:一月、二月,改为1月、2月)

要将 Element UI 的 <el-date-picker> 组件中的月份名称从中文大写&#xff08;如 "一月", "二月"&#xff09;更改为阿拉伯数字&#xff08;如 "1月", "2月"&#xff09;&#xff0c;需要进行一些定制化处理。可以通过国际化&a…

企业微信接入系列-上传临时素材

企业微信接入系列-上传临时素材 文档介绍上传临时素材写在最后 文档介绍 创建企业群发的文档地址&#xff1a;https://developer.work.weixin.qq.com/document/path/92135&#xff0c;在创建企业群发消息或者群发群消息接口中涉及到上传临时素材的操作&#xff0c;具体文档地址…

网络服务DHCP的安装

DHCP的安装 检查并且安装dhcp有关软件包 rpm -qc dhcp #检查是否存在dhcp yum install -y dhcp #进行yum安装查看系统的配置文件 切换到对应目录查看相关文件配置&#xff0c;发现是空目录。 将官方提供的example复制到原配置文件中 cp /usr/share/doc/dhcp-4.2.5/dhcpd.…

Python | 链表的基础操作1

面向对象&#xff1a; “对象”实际上是对现实世界中所存在的事物的一种抽象 人拥有着一些静态的特征&#xff0c;比如身高、体重、性别等&#xff0c;也拥有一些动态的行为&#xff0c;比如吃法&#xff0c;睡觉等&#xff0c;而在计算机世界中&#xff0c;我们将之抽象为一个…

什么是室内外一体化定位

室内外一体化定位是一种技术&#xff0c;它允许在室内外环境中对设备或人员进行连续、无缝的定位跟踪。这种技术结合了多种定位技术的优势&#xff0c;以克服单一技术在室内外环境中可能遇到的局限性。 室内外一体化定位通常涉及以下几种技术&#xff1a; 1. 卫星定位系统&am…

汽车软件单元测试分析

汽车软件单元测试概述 随着汽车技术的不断发展,汽车的功能日益复杂,软件在汽车中的作用也变得越来越重要。汽车嵌入式软件的质量直接关系到汽车的安全性、可靠性和性能表现。在这样的背景下,汽车软件单元测试成为了确保软件质量的关键环节。 汽车嵌入式软件单元测试是指对汽…

手撕C语言题典——相交链表

目录 前言 一&#xff0c;思路 1&#xff09;暴力 2)同步指针 二&#xff0c;代码实现 前言 依旧是力扣上的一道题&#xff0c;有许多新思路提供给我们 160. 相交链表 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/intersection-of-two-linked-li…