双向带头循环链表(图解)
文章目录
- 头节点(哨兵位)
- 双向循环结构
- 头插
- 尾插
- 头删
- 尾删
- 在指定位置之前插入数据
- 删除指定位置之前的数据
- 销毁链表
- 全部代码
- 结语
单链表地址
头节点(哨兵位)
什么是头节点呢?头节点也叫哨兵节点,他在链表中进行不了任何操作,只是用来放哨用的,在单链表中我们当我们尾插的时候我们呢需要判断此时的节点是否是空,如果不是空我们才可以尾插,是空的话我们直接赋值,而有了头节点我们就不需要判断是否为空了,直接在头节点后插入即可。这就是头节点的好处,尾插,尾删等也有好处(下文再说)
双向循环结构
在单链表中我们只有一个指针指向下一个节点,而双向链表则需要两个指针,一个指向前一个节点,一个指向后一个节点,由于我们需要循环起来,所以最后一个节点我们要指向头节点(这里是指第一个节点) 为了避免搞混我下文把第一个节点称为头节点,哨兵节点就为哨兵节点
哨兵节点如下:

注意这里我们是要创建一个哨兵节点,所以我们需要返回一个节点,而我们接下来的操作都是在哨兵节点的基础上进行的,所以每一次进行基本增删操作的时候可以传一级指针,因为我们接受的哨兵节点肯定是一个指针类型的。
结构定义如下:

代码实现:
//创建哨兵节点
List* ListHeadCreat()
{List* newhead = (List*)malloc(sizeof(List));if (newhead == NULL){printf("malloc fail!\n");exit(-1);}newhead->val = -1;newhead->next = newhead->prev = newhead;return newhead;
}
头插
首先我们要创建一个新的节点,先让这个新的节点指向自己
头插这里分两种情况:
1. 只有一个哨兵节点
(1)
newNode (新节点)的next指向phead,再让newNode的prev指向phead。
(2)
phead->next指向newNode,再让phead->prev指向newNode
2. 不止一个节点
(1)
newNode的next指向phead->prev(此时由于链表的最后一个元素就是哨兵节点的上一个节点),再让newNode->prev指phead
(2)
phead->prev指向newNode,phead->prev->next(最后一个节点的下一个节点)指向newNode

代码实现:
//头插
void ListPushFront(List* phead, LDataType x)
{assert(phead);List* newNode = BuyNode(x); newNode->prev = phead;newNode->next = phead->next;phead->next = newNode;//如果只有一个节点,要把此时的哨兵节点的上一个节点指向新插入的节点if (phead->prev == phead)phead->prev = newNode;
}

尾插
尾插无论是只有哨兵节点还是多个节点,代码操作都是一样的。
(1)
newNode->next指向phead
newNode->prev指向最后一个节点
(2)
phead->prev->next(最后一个节点)指向newNode
phead->prev(哨兵节点指向尾)指向newNode

代码实现:
//尾插
void ListPushBack(List* phead, LDataType x)
{assert(phead);List* newNode = BuyNode(x);newNode->next = phead;newNode->prev = phead->prev;phead->prev->next = newNode;phead->prev = newNode;
}

头删
头删我们得先保证链表中还有元素,所以我们需要判断以下phead->next是否还是Phead,如果是就说明此时只有哨兵节点
头删也是两种情况都是一样的代码
这里把第一个节点写为del,我们只需要让phead->next指向del,del->prev指向phead,这样就相当于把del从这个链表中断开了,接着就放心的free掉就可以了。

代码实现:
//头删
void ListPopFront(List* phead)
{assert(phead && phead->next != phead);List* del = phead->next;del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}

尾删
尾删跟头删差不多,只不过我们要找到最后一个节点(phead->prev),这就是双链表的好处
尾删的两种情况也都是一致的:
End = phead->prev(最后一个节点)
End->prev->next = phead
phead->next = End->prev
free(End)

代码实现:
//尾删
void ListPopBack(List* phead)
{assert(phead && phead->prev != phead);List* End = phead->prev;End->prev->next = phead;phead->prev = End->prev;free(End);End = NULL;
}

在指定位置之前插入数据
如下图:
无论在什么位置之前插入元素,都是一样的代码,首先我们还得写一个查找函数,这里只需要遍历找值就可以了,所以不单独写出来了,跟这里写一块了。

代码实现:
//查找指定元素
List* ListFind(List* phead, LDataType x)
{assert(phead && phead->next != phead);List* cur = phead->next;while (cur != phead){if (cur->val == x)return cur;cur = cur->next;}return NULL;
}//在指定位置之前插入元素
void ListInsertFront(List* pos, LDataType x)
{assert(pos);List* newNode = BuyNode(x);newNode->prev = pos->prev;newNode->next = pos;pos->prev->next = newNode;pos->prev = newNode;
}

删除指定位置之前的数据
这里需要注意,由于我们删除的是指定位置之前的数据,所以我们要保证这个pos->prev这个位置不能是哨兵节点,同时pos->prev->prev不能为空,必须有至少两个节点才能删除指定位置之前的数据

代码实现:
//删除指定位置之前的元素
void ListEarseFront(List* pos, List* phead)
{assert(pos && pos->prev && pos->prev != phead && pos->prev->prev);List* del = pos->prev;pos->prev->prev->next = pos;pos->prev = pos->prev->prev;free(del);del = NULL;
}

销毁链表
销毁链表分为两种方式:
(1)
由于我们是传入的phead是一级指针,而我们函数的参数也是用的一级指针接受的,因为函数的形参只是实参的一份临时拷贝,由于每次malloc的内存都是在堆区内开辟的所以我们可以做到释放每一个节点,但是phead这个指针却并不能置为空,如果不置空的话,他指向了被释放掉了的空间,还能找到那个空间,所以此时phead就是野指针,我们也只能在调用**ListDesTory()**这个销毁函数的下面手动把phead置为空。
代码实现:
void ListDesTory(List* phead)
{assert(phead);List* cur = phead->next;while (cur != phead){List* next = cur->next;free(cur);cur = next;}free(phead);
}
手动置空

(2)
当我们想要在函数内部修改变量的时候,我们因该传入它的地址,就像如果我们想要在函数里面修改 int a = 1这个a的值,那我们应该传入的是**(&a),同时我们呢要用int*** 的指针来接收,同样的这里我们呢传入**(&phead),那我们就要用(List**)**来接受,这样在函数内部就可以把phead置为空了
代码实现:
void ListDesTory(List** pphead)
{assert(pphead && *pphead);List* cur = (*pphead)->next;while (cur != (*pphead)){List* next = cur->next;free(cur);cur = next;}free((*pphead));*pphead = NULL;
}
全部代码
List.h
#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int LDataType;
typedef struct List
{struct List* prev;struct List* next;LDataType val;
}List;//创建哨兵节点
List* ListHeadCreat();
//打印节点
void ListPrint(List* phead);
//头插
void ListPushFront(List* phead,LDataType x);
//尾插
void ListPushBack(List* phead, LDataType x);//头删
void ListPopFront(List* phead);
//尾删
void ListPopBack(List* phead);//查找指定元素
List* ListFind(List* phead, LDataType x);
//在指定位置之前插入元素
void ListInsertFront(List* pos,LDataType x);
//删除指定位置之前的元素
void ListEarseFront(List* pos, List* phead);
//销毁链表
void ListDesTory(List* phead);
//销毁链表
//void ListDesTory(List** pphead);
List.c
#define _CRT_SECURE_NO_WARNINGS#include "Lish.h"//创建哨兵节点
List* ListHeadCreat()
{List* newhead = (List*)malloc(sizeof(List));if (newhead == NULL){printf("malloc fail!\n");exit(-1);}newhead->val = -1;newhead->next = newhead->prev = newhead;return newhead;
}//打印节点
void ListPrint(List* phead)
{assert(phead && phead->next);List* cur = phead->next;while (cur != phead){printf("%d ", cur->val);cur = cur->next;}
}List* BuyNode(LDataType x)
{List* newNode = (List*)malloc(sizeof(List));if (newNode == NULL){printf("malloc fail!\n");exit(-1);}newNode->val = x;newNode->next = newNode->prev = newNode;return newNode;
}
//头插
void ListPushFront(List* phead, LDataType x)
{assert(phead);List* newNode = BuyNode(x); newNode->prev = phead;newNode->next = phead->next;phead->next = newNode;//如果只有一个节点,要把此时的哨兵节点的上一个节点指向新插入的节点if (phead->prev == phead)phead->prev = newNode;
}
//尾插
void ListPushBack(List* phead, LDataType x)
{assert(phead);List* newNode = BuyNode(x);newNode->next = phead;newNode->prev = phead->prev;phead->prev->next = newNode;phead->prev = newNode;
}//头删
void ListPopFront(List* phead)
{assert(phead && phead->next != phead);List* del = phead->next;del->next->prev = phead;phead->next = del->next;free(del);del = NULL;
}
//尾删
void ListPopBack(List* phead)
{assert(phead && phead->prev != phead);List* End = phead->prev;End->prev->next = phead;phead->prev = End->prev;free(End);End = NULL;
}//查找指定元素
List* ListFind(List* phead, LDataType x)
{assert(phead && phead->next != phead);List* cur = phead->next;while (cur != phead){if (cur->val == x)return cur;cur = cur->next;}return NULL;
}//在指定位置之前插入元素
void ListInsertFront(List* pos, LDataType x)
{assert(pos);List* newNode = BuyNode(x);newNode->prev = pos->prev;newNode->next = pos;pos->prev->next = newNode;pos->prev = newNode;
}//删除指定位置之前的元素
void ListEarseFront(List* pos, List* phead)
{assert(pos && pos->prev && pos->prev != phead && pos->prev->prev);List* del = pos->prev;pos->prev->prev->next = pos;pos->prev = pos->prev->prev;free(del);del = NULL;
}
//销毁链表
void ListDesTory(List* phead)
{assert(phead);List* cur = phead->next;while (cur != phead){List* next = cur->next;free(cur);cur = next;}free(phead);
}
销毁链表
//void ListDesTory(List** pphead)
//{
// assert(pphead && *pphead);
// List* cur = (*pphead)->next;
// while (cur != (*pphead))
// {
// List* next = cur->next;
// free(cur);
// cur = next;
// }
// free((*pphead));
// *pphead = NULL;
//}
结语
OK了,链表在单链表和双向循环带头链表写完了就告一段落了,感觉有帮助的话可以点点赞,如果有哪写错了欢迎指出来~

相关文章:
双向带头循环链表(图解)
文章目录 头节点(哨兵位)双向循环结构头插尾插头删尾删在指定位置之前插入数据删除指定位置之前的数据销毁链表 全部代码结语 单链表地址 头节点(哨兵位) 什么是头节点呢?头节点也叫哨兵节点,他在链表中进行不了任何操作,只是用来放哨用的,在单链表中我们当我们尾插的时候我们…...
富文本编辑器 iOS
https://gitee.com/klkxxy/WGEditor-mobile#wgeditor-mobile 采用iOS系统浏览器做的一款富文本编辑器工具。 原理就是使用WKWebView加载一个本地的一个html文件,从而达到编辑器功能的效果! 由于浏览器的一些特性等,富文本编辑器手机端很难做…...
【OceanBase诊断调优】—— checksum error ret=-4103 问题排查
适用版本 OceanBase 数据库所有版本。 什么是 checksum data checksum:一个 SSTable 中所有宏块内存二进制计算出来的 checksum 值。反映了宏块中的数据和数据分布情况。如果宏块中数据一致但是数据分布不一致,计算出来的 checksum 也不相等。 column…...
融合Transformer与CNN,实现各任务性能巅峰,可训练参数减少80%
论文er看过来,今天给各位推荐一个热门创新方向:CNNTransformer。 众所周知,CNN通过多层卷积自动学习空间层级特征,能够有效提取图像局部特征。而Transformer通过自注意力机制全局建模,能够有效处理长距离依赖关系。 …...
K8s 多租户管理
一、K8s 多租户管理 多租户是指在同一集群中隔离多个用户或团队,以避免他们之间的资源冲突和误操作。在K8s中,多租户管理的核心目标是在保证安全性的同时,提高资源利用率和运营效率。 在K8s中,该操作可以通过命名空间࿰…...
Java面试题:Synchronized和Lock的对比
Synchronized和Lock对比 语法层面 Synchronized是关键字,源码在jvm中,用c语言实现 使用时,退出同步代码块时会自动释放 Lock是接口,源码由jdk提供,用java语言实现 使用时,需要手动调用unlock方法进行释放 功能层面 都属于悲观锁,具备基本的互斥,同步,锁重入功能 但Lock…...
VPN方案和特点
VPN方案和特点 VPN,或者称为虚拟专用网络,是一种保护你的在线安全和隐私的技术。它可以创建一个加密的连接,使你的在线活动对其他人不可见。以下是一些常见的VPN协议和它们的特点: 开放VPN (OpenVPN):这是一种极为可…...
力扣HOT100 - 84. 柱状图中最大的矩形
解题思路: 单调栈 对于一个高度height[ i ],找左右两边均严格小于它的值。 class Solution {public int largestRectangleArea(int[] heights) {int n heights.length;int[] left new int[n];int[] right new int[n];Deque<Integer> mono_st…...
【吃透Java手写】3-SpringBoot-简易版-源码解析
【吃透Java手写】SpringBoot-简易版-源码解析 1 SpringbootDemo2 准备工作2.1 Springboot-my2.1.1 依赖2.1.2 SpringBootApplication2.1.3 SJBSpringApplication2.1.3.1 run方法 2.2 Springboot-user2.2.1 依赖2.2.2 UserController2.2.3 UserApplication 2.3 分析run方法的逻辑…...
maven mirrorOf的作用
在工作中遇到了一个问题导致依赖下载不了,最后发现是mirror的问题,决定好好去看一下mirror的配置,以及mirrorOf的作用,以前都是直接复制过来使用,看了之后才明白什么意思。 过程 如果你设置了镜像,镜像会匹…...
Centos7 安装 MySQL5.7 使用 RPM 方式
1 访问网站 https://downloads.mysql.com/archives/community/ 选择合适的版本,点击 Download。 2 上传下载好的 mysql-5.7.44-1.el7.x86_64.rpm-bundle.tar 文件到 Centos7 机器,这里放到了 下载 目录。 3 解压 mysql-5.7.44-1.el7.x86_64.rpm-bundle.…...
代码随想录算法训练营day21 | 513.找树左下角的值、112. 路径总和、106.从中序与后序遍历序列构造二叉树
513.找树左下角的值 迭代法比较简单,层序遍历,找到最下面一层的第一个节点。题目已经说明节点数>1了 class Solution:def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:queue collections.deque()queue.append(root)result ro…...
微信小程序知识点归纳(一)
前言:适用于有一定基础的前端开发同学,完成从网页开发到小程序开发的知识转换。 先立框架,后砌墙壁 回顾:了解微信小程序开发流程-CSDN博客 初始页面结构,三部分pages、utils、配置,分别存放页面、工具类…...
wangEditor富文本编辑器与layui图片上传
记录:js 显示默认的wangEditor富文本编辑器内容和图片 <style>body {background-color: #ffffff;}.layui-form-select dl{z-index:100000;} </style> <div class"layui-form layuimini-form"><div class"layui-form-item"…...
爬虫学习:XPath提取网页数据
目录 一、安装XPath 二、XPath的基础语法 1.选取节点 三、使用XPath匹配数据 1.浏览器审查元素 2.具体实例 四、总结 一、安装XPath 控制台输入指令:pip install lxml 二、XPath的基础语法 XPath是一种在XML文档中查找信息的语言,可以使用它在HTM…...
【雅思写作】Vince9120雅思小作文笔记——P1 Intro(前言)
文章目录 链接P1 Intro(前言)字数限制题型综述(problem types overview)1. **柱状图(Bar Chart)** - 描述不同类别在某个或多个变量上的数据量比较。2. **线图(Line Graph)** - 展示…...
【面试干货】HTTPS 工作原理
【面试干货】HTTPS 工作原理 1、握手阶段(Handshake)2、密钥协商阶段3、加密通信阶段4、结束通信阶段 💖The Begin💖点点关注,收藏不迷路💖 HTTPS(HyperText Transfer Protocol Secureÿ…...
Cocos Creator 中编码规范 (6)
Cocos中命名规范 创建文件夹,全小写。创建脚本,首字母大写的驼峰形式。创建变量,首字母小写的驼峰形式 官方的编码规范...
Vue3:menu导航栏出现多个同一跳转路径的菜单处理
文章目录 需求整理实现思路实现过程 需求整理,实现思路 最近公司想将之前老的项目整理出来,因为这个老项目内容太杂什么页面都往里面塞,导致菜单特别多,公司就像将这个老的项目迁出来,这个旧的项目本来是后端PHP写的。…...
SAM轻量化应用Auto-SAM、Group-Mix SAM、RAP-SAM、STLM
1. Auto SAM(Auto-Prompting SAM for Mobile Friendly 3D Medical Image Segmentation) 1.1 面临问题 医学背景: (1)与自然图像相比,医学图像的尺寸更小,形状不规则,对比度更低。&…...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...
