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

双向带头循环链表(图解)

文章目录

  • 头节点(哨兵位)
  • 双向循环结构
    • 头插
    • 尾插
    • 头删
    • 尾删
    • 在指定位置之前插入数据
    • 删除指定位置之前的数据
    • 销毁链表
  • 全部代码
  • 结语

单链表地址

头节点(哨兵位)

什么是头节点呢?头节点也叫哨兵节点,他在链表中进行不了任何操作,只是用来放哨用的,在单链表中我们当我们尾插的时候我们呢需要判断此时的节点是否是空,如果不是空我们才可以尾插,是空的话我们直接赋值,而有了头节点我们就不需要判断是否为空了,直接在头节点后插入即可。这就是头节点的好处,尾插,尾删等也有好处(下文再说)

双向循环结构

单链表中我们只有一个指针指向下一个节点,而双向链表则需要两个指针,一个指向前一个节点,一个指向后一个节点,由于我们需要循环起来,所以最后一个节点我们要指向头节点(这里是指第一个节点) 为了避免搞混我下文把第一个节点称为头节点,哨兵节点就为哨兵节点

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

结构定义如下:
在这里插入图片描述

代码实现:

//创建哨兵节点
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,再让newNodeprev指向phead
(2)
phead->next指向newNode,再让phead->prev指向newNode
2. 不止一个节点
(1)
newNode的next指向phead->prev(此时由于链表的最后一个元素就是哨兵节点上一个节点),再让newNode->prevphead
(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文件&#xff0c;从而达到编辑器功能的效果&#xff01; 由于浏览器的一些特性等&#xff0c;富文本编辑器手机端很难做…...

【OceanBase诊断调优】—— checksum error ret=-4103 问题排查

适用版本 OceanBase 数据库所有版本。 什么是 checksum data checksum&#xff1a;一个 SSTable 中所有宏块内存二进制计算出来的 checksum 值。反映了宏块中的数据和数据分布情况。如果宏块中数据一致但是数据分布不一致&#xff0c;计算出来的 checksum 也不相等。 column…...

融合Transformer与CNN,实现各任务性能巅峰,可训练参数减少80%

论文er看过来&#xff0c;今天给各位推荐一个热门创新方向&#xff1a;CNNTransformer。 众所周知&#xff0c;CNN通过多层卷积自动学习空间层级特征&#xff0c;能够有效提取图像局部特征。而Transformer通过自注意力机制全局建模&#xff0c;能够有效处理长距离依赖关系。 …...

K8s 多租户管理

一、K8s 多租户管理 多租户是指在同一集群中隔离多个用户或团队&#xff0c;以避免他们之间的资源冲突和误操作。在K8s中&#xff0c;多租户管理的核心目标是在保证安全性的同时&#xff0c;提高资源利用率和运营效率。 在K8s中&#xff0c;该操作可以通过命名空间&#xff0…...

Java面试题:Synchronized和Lock的对比

Synchronized和Lock对比 语法层面 Synchronized是关键字,源码在jvm中,用c语言实现 使用时,退出同步代码块时会自动释放 Lock是接口,源码由jdk提供,用java语言实现 使用时,需要手动调用unlock方法进行释放 功能层面 都属于悲观锁,具备基本的互斥,同步,锁重入功能 但Lock…...

VPN方案和特点

VPN方案和特点 VPN&#xff0c;或者称为虚拟专用网络&#xff0c;是一种保护你的在线安全和隐私的技术。它可以创建一个加密的连接&#xff0c;使你的在线活动对其他人不可见。以下是一些常见的VPN协议和它们的特点&#xff1a; 开放VPN (OpenVPN)&#xff1a;这是一种极为可…...

力扣HOT100 - 84. 柱状图中最大的矩形

解题思路&#xff1a; 单调栈 对于一个高度height[ i ]&#xff0c;找左右两边均严格小于它的值。 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的作用

在工作中遇到了一个问题导致依赖下载不了&#xff0c;最后发现是mirror的问题&#xff0c;决定好好去看一下mirror的配置&#xff0c;以及mirrorOf的作用&#xff0c;以前都是直接复制过来使用&#xff0c;看了之后才明白什么意思。 过程 如果你设置了镜像&#xff0c;镜像会匹…...

Centos7 安装 MySQL5.7 使用 RPM 方式

1 访问网站 https://downloads.mysql.com/archives/community/ 选择合适的版本&#xff0c;点击 Download。 2 上传下载好的 mysql-5.7.44-1.el7.x86_64.rpm-bundle.tar 文件到 Centos7 机器&#xff0c;这里放到了 下载 目录。 3 解压 mysql-5.7.44-1.el7.x86_64.rpm-bundle.…...

代码随想录算法训练营day21 | 513.找树左下角的值、112. 路径总和、106.从中序与后序遍历序列构造二叉树

513.找树左下角的值 迭代法比较简单&#xff0c;层序遍历&#xff0c;找到最下面一层的第一个节点。题目已经说明节点数>1了 class Solution:def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:queue collections.deque()queue.append(root)result ro…...

微信小程序知识点归纳(一)

前言&#xff1a;适用于有一定基础的前端开发同学&#xff0c;完成从网页开发到小程序开发的知识转换。 先立框架&#xff0c;后砌墙壁 回顾&#xff1a;了解微信小程序开发流程-CSDN博客 初始页面结构&#xff0c;三部分pages、utils、配置&#xff0c;分别存放页面、工具类…...

wangEditor富文本编辑器与layui图片上传

记录&#xff1a;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 控制台输入指令&#xff1a;pip install lxml 二、XPath的基础语法 XPath是一种在XML文档中查找信息的语言&#xff0c;可以使用它在HTM…...

【雅思写作】Vince9120雅思小作文笔记——P1 Intro(前言)

文章目录 链接P1 Intro&#xff08;前言&#xff09;字数限制题型综述&#xff08;problem types overview&#xff09;1. **柱状图&#xff08;Bar Chart&#xff09;** - 描述不同类别在某个或多个变量上的数据量比较。2. **线图&#xff08;Line Graph&#xff09;** - 展示…...

【面试干货】HTTPS 工作原理

【面试干货】HTTPS 工作原理 1、握手阶段&#xff08;Handshake&#xff09;2、密钥协商阶段3、加密通信阶段4、结束通信阶段 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff…...

Cocos Creator 中编码规范 (6)

Cocos中命名规范 创建文件夹&#xff0c;全小写。创建脚本&#xff0c;首字母大写的驼峰形式。创建变量&#xff0c;首字母小写的驼峰形式 官方的编码规范...

Vue3:menu导航栏出现多个同一跳转路径的菜单处理

文章目录 需求整理实现思路实现过程 需求整理&#xff0c;实现思路 最近公司想将之前老的项目整理出来&#xff0c;因为这个老项目内容太杂什么页面都往里面塞&#xff0c;导致菜单特别多&#xff0c;公司就像将这个老的项目迁出来&#xff0c;这个旧的项目本来是后端PHP写的。…...

SAM轻量化应用Auto-SAM、Group-Mix SAM、RAP-SAM、STLM

1. Auto SAM&#xff08;Auto-Prompting SAM for Mobile Friendly 3D Medical Image Segmentation&#xff09; 1.1 面临问题 医学背景&#xff1a; &#xff08;1&#xff09;与自然图像相比&#xff0c;医学图像的尺寸更小&#xff0c;形状不规则&#xff0c;对比度更低。&…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

比较数据迁移后MySQL数据库和OceanBase数据仓库中的表

设计一个MySQL数据库和OceanBase数据仓库的表数据比较的详细程序流程,两张表是相同的结构,都有整型主键id字段,需要每次从数据库分批取得2000条数据,用于比较,比较操作的同时可以再取2000条数据,等上一次比较完成之后,开始比较,直到比较完所有的数据。比较操作需要比较…...

elementUI点击浏览table所选行数据查看文档

项目场景&#xff1a; table按照要求特定的数据变成按钮可以点击 解决方案&#xff1a; <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...

【深度学习新浪潮】什么是credit assignment problem?

Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...

02.运算符

目录 什么是运算符 算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符 关系运算符 逻辑运算符 &&&#xff1a;逻辑与 ||&#xff1a;逻辑或 &#xff01;&#xff1a;逻辑非 短路求值 位运算符 按位与&&#xff1a; 按位或 | 按位取反~ …...