数据结构第六弹---带头双向循环链表
双向循环链表
- 1、带头双向循环链表概念
- 2、带头双向循环链表的优势
- 3、带头双向循环链表的实现
- 3.1、头文件包含和结构定义
- 3.2、创建新结点
- 3.3、打印
- 3.4、初始化
- 3.5、销毁
- 3.6、尾插
- 3.7、头插
- 3.8、头删
- 3.9、尾删
- 3.10、查找
- 3.11、在pos之前插入
- 3.12、删除pos位置
- 3.13、判断是否为空
- 3.14、计算大小
- 4、代码汇总
- 总结
1、带头双向循环链表概念

概念:带头双向循环链表是一种特殊类型的链表,它由一系列节点组成,每个
节点包含一个数据域和两个指针域,第一个结点不存储有效数据。其中一个指
针指向下一个节点,另一个指针指向前一个节点。在带头双向循环链表中,首
节点的前一个节点是尾节点,尾节点的下一个节点是首节点,形成一个闭环。
2、带头双向循环链表的优势
1.高效遍历:由于带头双向循环链表可以双向遍历,因此可以在O(1)时间内访问任何节点。
2.内存高效:与双向链表相比,带头双向循环链表不需要额外的内存来存储头部节点。
3.插入和删除操作高效:在带头双向循环链表中插入和删除节点时,只需调整指针即可,无需移动大量数据。
3、带头双向循环链表的实现
实现一个带头双向循环链表首先得创建一个工程。(下图为vs 2022)

List.h(带头双向循环链表的类型定义、接口函数声明、引用的头文件)
List.c(带头双向循环链表接口函数的实现)
test.c (主函数、测试顺序表各个接口功能)
以下是List.h的代码。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int LTDataType;
typedef struct ListNode
{LTDataType data;struct ListNode* next;struct ListNode* prev;
}ListNode;//双向链表打印
void ListPrint(ListNode* phead);
//双向链表初始化
ListNode* ListInit();
//双向链表销毁
void ListDestory(ListNode* phead);
//双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x);
//头插
void ListPushFront(ListNode* phead, LTDataType x);
//头删
void ListPopFront(ListNode* phead);
//尾删
void ListPopBack(ListNode* phead);
//查找
ListNode* ListFind(ListNode* phead, LTDataType x);
//在pos之前插入
void ListInsert(ListNode* pos, LTDataType x);
//删除pos位置
void ListErase(ListNode* pos);
//判断是否为空
bool ListEmpty(ListNode* phead);
//计算大小
int ListSize(ListNode* phead);
3.1、头文件包含和结构定义
以下是实现双向循环链表可能用到的头文件。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
以下是博主创建的双向循环链表的结构,可以根据自己的喜好创建喔。
建议:创建结构时最好能通俗易懂,最好不用拼音创建。
typedef int LTDataType;//定义数据类型,可以根据需要更改
typedef struct ListNode
{LTDataType data; //数据域 存储数据struct ListNode* next;//指针域 存储指向下一个结点的指针struct ListNode* prev;//指针域 存储指向前一个结点的指针
}ListNode;
3.2、创建新结点
为什么先创建新结点而不是初始化呢?因为当前链表为带头的链表,初始化时需要创建结点,所以就先封装创建结点函数。

ListNode* BuyList(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){printf("malloc fail\n");exit(-1);}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
3.3、打印

void ListPrint(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){printf("%d ", cur->data);cur = cur->next;}printf("\n");
}
3.4、初始化

ListNode* ListInit()
{ListNode* phead = BuyList(0);phead->next = phead;//构成循环phead->prev = phead;//构成循环return phead;
}
3.5、销毁

void ListDestory(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){ListNode* next = cur->next;free(cur);cur = next;}free(phead);phead = NULL;//养成好习惯,释放之后手动置为NULL
}
3.6、尾插

void ListPushBack(ListNode* phead, LTDataType x)
{assert(phead);//1.创建结点ListNode* newnode = BuyList(x);ListNode* tail = phead->prev;//先找到尾结点//2.链接nexttail->next = newnode;newnode->prev = tail;//3.链接prevnewnode->next = phead;phead->prev = newnode;}
尾插测试
建议养成有初始化函数就有销毁函数的习惯。

3.7、头插

void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);ListNode* newnode = BuyList(x);ListNode* first = phead->next;phead->next = newnode;newnode->prev = phead;newnode->next = first;first->prev = newnode;
}
头插测试

3.8、头删

void ListPopFront(ListNode* phead)
{assert(phead);assert(phead->next != phead);//没有数据则报错ListNode* first = phead->next;ListNode* second = first->next;phead->next = second;second->prev = phead;free(first);first = NULL;
}
测试头删


3.9、尾删

void ListPopBack(ListNode* phead)
{assert(phead);assert(phead->next != phead);ListNode* tail = phead->prev;ListNode* prev = tail->prev;prev->next = phead;phead->prev = prev;free(tail);tail = NULL;
}
尾删测试

3.10、查找
思想:遍历一遍链表,如果该结点的data等于x则返回该结点的地址,遍历一遍没有找到则返回NULL,跟后面在pos位置插入函数结合起来用。
ListNode* ListFind(ListNode* phead, LTDataType x)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
3.11、在pos之前插入
跟头插尾插思想差不多,可以自己画图理解理解喔,如果有不理解的可以私信博主喔!这里就没有画图啦!
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = BuyList(x);ListNode* prev = pos->prev;prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}
测试

3.12、删除pos位置
void ListErase(ListNode* pos)
{assert(pos);ListNode* prev = pos->prev;ListNode* next = pos->next;prev->next = pos->next;next->prev = prev;
}

3.13、判断是否为空
bool ListEmpty(ListNode* phead)
{assert(phead);return phead->next == phead;//相等则为真,不相等则为假
}

3.14、计算大小
思想:创建一个size变量,从头结点的下一个结点遍历链表,不等于头结点则将size++。
int ListSize(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;int size = 0;while (cur != phead){size++;cur = cur->next;}return size;
}
测试

4、代码汇总
以下是SList.c的代码
//创建结点
ListNode* BuyList(LTDataType x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){printf("malloc fail\n");exit(-1);}newnode->data = x;newnode->next = NULL;newnode->prev = NULL;return newnode;
}
//打印
void ListPrint(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){printf("%d ", cur->data);cur = cur->next;}printf("\n");
}
//初始化
ListNode* ListInit()
{ListNode* phead = BuyList(0);phead->next = phead;//构成循环phead->prev = phead;//构成循环return phead;
}
//销毁
void ListDestory(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){ListNode* next = cur->next;free(cur);cur = next;}free(phead);phead = NULL;//养成好习惯,释放之后手动置为NULL
}
//尾插
void ListPushBack(ListNode* phead, LTDataType x)
{assert(phead);ListNode* newnode = BuyList(x);ListNode* tail = phead->prev;tail->next = newnode;newnode->prev = tail;newnode->next = phead;phead->prev = newnode;
}
//头插
void ListPushFront(ListNode* phead, LTDataType x)
{assert(phead);ListNode* newnode = BuyList(x);ListNode* first = phead->next;phead->next = newnode;newnode->prev = phead;newnode->next = first;first->prev = newnode;}
//头删
void ListPopFront(ListNode* phead)
{assert(phead);assert(phead->next != phead);ListNode* first = phead->next;ListNode* second = first->next;phead->next = second;second->prev = phead;free(first);first = NULL;
}
//尾删
void ListPopBack(ListNode* phead)
{assert(phead);assert(phead->next != phead);ListNode* tail = phead->prev;ListNode* prev = tail->prev;prev->next = phead;phead->prev = prev;free(tail);tail = NULL;
}
//查找元素为X的地址
ListNode* ListFind(ListNode* phead, LTDataType x)
{assert(phead);ListNode* cur = phead->next;while (cur != phead){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
//在pos之前插入
void ListInsert(ListNode* pos, LTDataType x)
{assert(pos);ListNode* newnode = BuyList(x);ListNode* prev = pos->prev;prev->next = newnode;newnode->prev = prev;newnode->next = pos;pos->prev = newnode;
}
//删除pos位置
void ListErase(ListNode* pos)
{assert(pos);ListNode* prev = pos->prev;ListNode* next = pos->next;prev->next = pos->next;next->prev = prev;
}
//判断是否为空
bool ListEmpty(ListNode* phead)
{assert(phead);//1.//if (phead->next == phead)//{// return true;//}//else//{// return false;//}//2.return phead->next == phead;
}
//获取有效数据个数
int ListSize(ListNode* phead)
{assert(phead);ListNode* cur = phead->next;int size = 0;while (cur != phead){size++;cur = cur->next;}return size;
}
总结
本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!
相关文章:
数据结构第六弹---带头双向循环链表
双向循环链表 1、带头双向循环链表概念2、带头双向循环链表的优势3、带头双向循环链表的实现3.1、头文件包含和结构定义3.2、创建新结点3.3、打印3.4、初始化3.5、销毁3.6、尾插3.7、头插3.8、头删3.9、尾删3.10、查找3.11、在pos之前插入3.12、删除pos位置3.13、判断是否为空3…...
洛谷——P1347 排序(图论-拓扑排序)
文章目录 一、题目排序题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 样例 #2样例输入 #2样例输出 #2 样例 #3样例输入 #3样例输出 #3 提示 二、题解基本思路:代码 一、题目 排序 题目描述 一个不同的值的升序排序数列指的是一个从左到右元素依次增大的…...
JVM内存管理
一.java程序运行过程 JDK,JRE,JVM JVM把我们的字节码翻译成机械能执行的机械码。 JRE除了包含JVM之外,还包含很多java的原生依赖库。 JDK除了包含JRE之外,还包含很多工具,比如javac工具。 .java文件是怎么被执行的 我们的.java文件会被…...
将 Python 和 Rust 融合在一起,为 pyQuil® 4.0 带来和谐
文章目录 前言设定方向从 Rust 库构建 Python 软件包改装 pyQuil异步困境回报:功能和性能结论 前言 pyQuil 一直是在 Rigetti 量子处理单元(QPUs)上构建和运行量子程序的基石,通过我们的 Quantum Cloud Services(QCS™…...
Spring Boot应用程序中VO的理解及使用
在Spring Boot应用程序中,VO(View Object)通常用于表示视图层所需的数据,这些数据来自于业务逻辑层或数据访问层。VO的主要目的是将业务逻辑层的数据结构转换为视图层可以使用的数据结构,使得视图层可以直接使用VO中的…...
华为交换机ETH-TRUNK链路聚合lacp模式与手工模式
SW1配置如下 vlan batch 10interface Eth-Trunk1port link-type trunkport trunk allow-pass vlan 10mode lacp-static #手工模式删除改行max active-linknumber 2 #手工模式删除改行trunkport GigabitEthernet 0/0/1 to 0/0/2#配置为主设备(修改优先级&…...
函数图像化
函数图像化 在进行模型提取时,往往会需要选择拟合的函数,因此,了解函数的图像对于模型拟合提取有益,以下是常见的一些函数的曲线 1 二次函数 常见的耳二次函数曲线,转换x与y数量级差异仅一个数量级, 2 三…...
gnu工程的编译 - 以libiconv为例
文章目录 gnu工程的编译 - 以libiconv为例概述gnu官方源码包的发布版从官方的代码库直接迁出的git版源码如果安装了360, 需要添加开发相关的目录到信任区生成 configrue 的方法备注END gnu工程的编译 - 以libiconv为例 概述 gnu工程的下载分2种: gnu官方源码包的发布版 这种…...
在 CentOS 7.8 上安装 Node.js
1.安装 NVM(Node Version Manager): curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash这将从 NVM 的 GitHub 仓库下载安装脚本并执行。请注意,您需要重新启动终端或者执行 source ~/.bashrc 以…...
【数据分析实战】冰雪大世界携程景区评价信息情感分析采集词云
文章目录 引言数据采集数据集展示数据预处理 数据分析评价总体情况分析本人浅薄分析 各游客人群占比分析本人浅薄分析 各评分雷达图本人浅薄分析 差评词云-可视化本人浅薄分析 好评词云-可视化本人浅薄分析 综合分析写在最后 今年冬天,哈尔滨冰雪旅游"杀疯了&q…...
BIND-DNS配置介绍
一、主要配置文件 /etc/named.conf options { //Option 段全部配置 listen-on port 53 { 127.0.0.1; };//表示BIND将在53端口监听,若需要对所有IP进行监听,则修改为// listen-on port 53 { any; }; directory "/var/named"…...
Python技巧
Python,现如今非常热门的一种编程语言,在人工智能中大放异彩。做任何事都需要技巧,这可以大大提高效率,学习Python,同样如此! 第一个就是assret语句,让我们看下面一个关于折扣的例子: def dic…...
几种常见的CSS三栏布局?介绍下粘性布局(sticky)?自适应布局?左边宽度固定,右边自适应?两种以上方式实现已知或者未知宽度的垂直水平居中?
几种常见的CSS三栏布局 流体布局 效果: 参考代码: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1…...
箭头函数 - JavaScript的新宠儿
📢 鸿蒙专栏:想学鸿蒙的,冲 📢 C语言专栏:想学C语言的,冲 📢 VUE专栏:想学VUE的,冲这里 📢 CSS专栏:想学CSS的,冲这里 Ǵ…...
操作系统期末复习知识点
目录 一.概论 1.操作系统的介绍 2.特性 3.主要功能 4.作用 二.进程的描述与控制 1.进程的定义 2.特性 3.进程的创建步骤 4.基本状态转化 5.PCB的作用 6.进程与线程的比较 三.进程同步 1.同步的概念(挺重要的) 2.临界区 3.管程和进程的区…...
[英语学习][23][Word Power Made Easy]的精读与翻译优化
[序言] 译者的这次翻译, 完全直译, 生硬无比. [英文学习的目标] 提升自身的英语水平, 对日后编程技能的提升有很大帮助. 希望大家这次能学到东西, 同时加入我的社区讨论与交流英语相关的内容. [原著英文与翻译版对照][第22页] Knowledge is chiefly in the form of words…...
吉林大学19、21级计算机学院《计算机网络》期末真题试题
一、21级(考后回忆) 一、不定项选择(一共10个选择题,一个两分,选全得满分) 不定项:可以选择1~4个 考点有: ①协议、服务 ②码分多路复用通过接受码片序列,求哪个站点发送…...
python练习3【题解///考点列出///错题改正】
一、单选题 1.【单选题】 ——可迭代对象 下列哪个选项是可迭代对象( D)? A.(1,2,3,4,5) B.[2,3,4,5,6] C.{a:3,b:5} D.以上全部 知识点补充——【可迭代对象】 可迭代对象(iterable)是指可以通过迭代ÿ…...
LINUX服务器防火墙nf_conntrack问题一例
一、故障现象 业务反馈服务异常,无法响应请求,从系统日志 dmesg 或 /var/log/messages 看到大量以下记录:kernel: nf_conntrack: table full, dropping packet. 二、问题分析 业务高峰期服务器访问量大,内核 netfilter 模块 conntrack 相关参…...
经典八股文之RocketMQ
核心概念 NameServer nameserver是整个rocketmq的大脑,是rocketmq的注册中心。broker在启动时向所有nameserver注册。生产者在发送消息之前先从 NameServer 获取 Broker 服务器地址列表(消费者一 样),然后根据负载均衡算法从列表中选择一台服务器进行消…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
