数据结构学习分享之双向链表详解

news2024/10/2 6:25:52

数据结构第四课

  • 1.前言
  • 2. 结构分析
  • 3. 双链表的实现
    • 3.1 初始化结构
    • 3.2 初始化函数
    • 3.3 尾插函数
    • 3.4 尾删函数
    • 3.5 头插函数
    • 3.6 头删函数
    • 3.7 销毁链表
    • 3.8 其他函数
  • 4. 缓存利用率
  • 5. 总结


1.前言

    💓博主CSDN:杭电码农-NEO💓🎉🎉🎉

    ⏩专栏分类:数据结构学习分享(持续更新中🫵)⏪🎉🎉🎉

    我们上一期说到,两链表中有两个最常用的结构,一个是最简单的无头不循环单向链表,还有一个就是 结构相对比较复杂的带头双向循环链表 ,我们这一章节就来分享双向带头循环链表的具体实现:

要看所有代码的朋友:        💯 我的码云放在了最后 💯
在这里插入图片描述



2. 结构分析

与我们上一章讲的单链表不同,这里双向带头循环链表的结构要复杂一点,主要需要注意这几个点:

  • 定义结构体时,单链表只需要定义一个指针next,双链表需要定义额外一个指针:prev用来指向上一个节点

  • 带头(带哨兵位)的链表在初始化时就要给哨兵位开辟一份空间,并且哨兵位不存储数据,第一个节点要链接在哨兵位的下一个位置

  • 循环的链表的尾节点的next不能指向NULL,要指向第一个节点,形成循环结构

  • 我们在进行插入删除的时候可以不传二级指针!因为我们拥有了哨兵位作为我们指针指向的第一个位置,并且哨兵位是不会改变的!

  • 不循环的链表判断尾节点是cur->next == NULL时,然而循环的带头链表判断尾节点是cur->next==head时,这里的head指的是哨兵位


3. 双链表的实现

单链表我们使用的名字是SList,意为single list 就是单个的意思,这里我们直接用List表示双链表

3.1 初始化结构

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<malloc.h>
typedef int LTDateType;//随时可以改变类型

typedef struct ListNode
{
	LTDateType data;
	struct ListNode* prev;//指向上一个节点
	struct ListNode* next;//指向下一个节点
}LTNode;//简化名字

3.2 初始化函数

在我们写初始化函数之前我们要先明白一点,那就是当链表为空时,我们哨兵位的next和prev都指向自己本身!

在这里插入图片描述


LTNode* ListInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));//为哨兵位开辟一个节点
	phead->next = phead;
	phead->prev = phead;
	return phead;//将phead返回后,在test.c文件中赋值给结构体指针
}
void TestList1()
{
	LTNode* plist = ListInit();//plist里面存储一个哨兵位
}

3.3 尾插函数

我们说存储数据的结构都离不开增删查改,这里我们定义的双向带头循环链表进行增删查改是很简单的

void ListPushBack(LTNode* phead, LTDateType x)//尾插
{
	assert(phead);
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));//为插入的数据开辟空间
	if (newnode == NULL)//这个if语句是为了判断我们的动态开辟空间有没有失败
	{
		printf("fail");
		exit(-1);
	}
	newnode->data = x;//将数据x给上
	LTNode* tail = phead->prev;//这里也可以不定义tail,直接用phead->prev表示尾
    tail->next = newnode;//尾节点与新节点相连
	newnode->prev = tail;//新的尾节点的prev与旧的尾相连
	newnode->next = phead;//新的尾节点与头相连形成循环
	phead->prev = newnode;
}

我们发现当我们定义链表结构为双向带头循环链表时,插入数据是很方便的,只需要判断好链接关系就可以了,而我们之前的单链表还需要判断链表为空的情况,这种情况要拿出来特殊处理,但是这个地方当链表为空时也是没有问题的!

在这里插入图片描述


3.4 尾删函数

我们有了之前的基础后,直接上代码!

void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);//不能把哨兵为给删除了
	LTNode* tail = phead->prev;
	LTNode* prev = tail->prev;//后面就处理链接关系
	free(tail);
	tail = NULL;
	prev->next = phead;
	phead->prev = prev;
}

值得注意的是,这里的assert(phead->next!=phead)是当我们的phead->next等于自己本身时,代表我们链表中已经没有数据了,只剩下一个哨兵位了.这时需要断言报错 👍👍👍


3.5 头插函数

void ListPushFront(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));//只要是插入数据就需要开辟空间
	newnode->data = x;
	LTNode* next = phead->next;//头插相当于就是插入在哨兵位后面
	phead->next = newnode;//注意链接关系别写漏就没问题
	newnode->prev = phead;
	newnode->next = next;
	next->prev = newnode;
}

同样的,当我们链表中没有数据时,这时的头插也是没有问题的,这里我就不做过多说明


3.6 头删函数

void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* next = phead->next;
	phead->next = next->next;
	next->next->prev = phead;
	free(next);
	next = NULL;
}

头删和尾删一样,不能把哨兵位一起删了,并且需要注意一点,我们的free不能太早使用,不然我们就找不到我们next的next和next的prev了,所以我们要把所有链接关系全部修改完之后,才能把next的空间给释放掉


3.7 销毁链表

void ListDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur!=phead)//释放掉所有的数据空间
	{
		LTNode* next = cur->next;//这里需要定义一个next指针,因为我们把cur给释放掉后就找不到cur->next的了
		free(cur);
		cur = next;
	}
	free(phead);//最后将哨兵位也释放掉,然后置空
	phead = NULL;
}

3.8 其他函数

这里我给出几个除了头插头删,尾插尾删的函数.

  • Find函数:返回与x值相同的节点
LTNode* ListFind(LTNode* phead, LTDateType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while ( cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

  • Insert函数:在pos位置之前插入数据
void ListInsert(LTNode* pos, LTDateType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* next = pos->next;
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	newnode->data = x;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
	prev->next = newnode;
}

  • Erase函数:删除pos位置的节点
void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* next = pos->next;
	prev->next = next;
	next->prev = prev;
    free(pos);
	pos = NULL;
}


4. 缓存利用率

我们上一章总结顺序表和链表的区别的时候提到:顺序表的缓存利用率高,链表的缓存利用率低,那么到底什么是缓存利用率?这里我给大家大致提一下:我们的计算器存在很多存储方式:

在这里插入图片描述

我们在数组中开辟空间和在链表上开辟空间时,这种缓存的命中是不一样的,有兴趣了解的朋友具体的细节可以参考这篇文章CPU缓存知识.


5. 总结

我们关于链表的知识的分享就要告一段落了,但是链表这一章节虽然只有两次分享,但是它在诸多面试题中考察的概率是很高的,特别是单链表,因为我们知道单链表是有很多缺陷的,所以在校招和很多OJ题中,单链表考察的地方非常之多,建议多刷刷题找找思路,我的专栏单链表OJ中也总结了不少OJ题的思路以及衍生问题,有兴趣的朋友可以来指导指导.

💕 我的码云:gitee-杭电码农-NEO💕

🔎 下期预告:栈的实现 🔍

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

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

相关文章

Python(一) 基础二(语句、文件读写)

1.语句 1.1.if…elif…else 类似于java的if…else if…else语句 1.1.1.判断条件 比较运算符: 、>、<、<、>、!、is、is not、in、not in 1.1.2.和is的区别 list_1 [aaa, bbb] list_2 [aaa, bbb] print(list_1 list_2) #结果:True print(list_1 is list_2)…

async await

async await async await 都是修饰符&#xff0c;修饰函数的。 async/await一定是成对出现的。比如用async也没有什么太大意义。只要函数体中出现了await&#xff0c;则当前函数必须用async来修饰。 用async修饰的函数&#xff0c;相当于用promise包裹起来。其实相当于把同步修…

基于jeecgboot的OA日程安排开发(一)

日程安排也是OA里的一项重要功能&#xff0c;所以基于jeecgboot开发这个日程安排。 日程安排主要涉及以下几个方面&#xff1a; 1、数据库方面&#xff0c;主要是分日历与日程 日历可以分个人日历与工作日历&#xff0c;一般情况下&#xff0c;个人日历只给自己查看&#xff0…

2023-05-02 动态规划简介

动态规划简介 1 动态规划的基本概念 阶段、状态、决策、策略、状态转移方程 1) 阶段和阶段变量 将问题的全过程恰当地分成若干个相互联系的阶段闫氏DP分析法&#xff1a;对应f[i][j]的ij遍历时形成的所有f[i][j]阶段的划分一般根据时间和空间的自然特征去划分阶段的划分便于把…

Nginx原理解析

master和worker 当linux启动的时候&#xff0c;会有两个和nginx相关的进程&#xff0c;一个是master,一个是worker。 master如何工作 当客户端发送请求到nginx之后&#xff0c;master会接收到这个请求&#xff0c;然后通知所有的worker进程&#xff0c;此时&#xff0c;work…

【STL十六】函数对象:包装器(std::function)——绑定器(std::bind)——函数适配器

【STL十六】函数对象&#xff1a;包装器(std::function&#xff09;——绑定器&#xff08;std::bind&#xff09;——函数适配器 一、包装器(std::function&#xff09;1、简介2、头文件3、构造函数4、demo5、异常 二、绑定器&#xff08;std::bind&#xff09;1、简介2、头文…

Nessus漏洞扫描以及OpenSSH漏洞修复验证

主机IP地址资源kali192.168.200.1285GB内存/4CPUCentOS7.5192.168.200.1292GB内存/2CPU https://www.tenable.com/downloads/nessus?loginAttemptedtrue curl --request GET \--url https://www.tenable.com/downloads/api/v2/pages/nessus/files/Nessus-10.5.1-ubuntu1404_am…

云原生Istio案例实战

目录 1 Istio监控功能1.1 prometheus和grafana1.2 访问prometheus1.3 访问grafana 2 项目案例&#xff1a;bookinfo2.1 理解什么是bookinfo2.2 sidecar自动注入到微服务2.3 启动bookinfo2.4 通过ingress方式访问2.5 通过istio的ingressgateway访问2.5.1 确定 Ingress 的 IP 和端…

计算机视觉--图像拼接

图像拼接 单应性变换仿射变换图像扭曲实现图像嵌入&#xff08;图中图&#xff09; RANSAC算法算法介绍图片收集无RANSAC优化和有RANSAC优化的代码实现差别 总结 单应性变换 单应性变换是指一个平面上的点通过一个矩阵变换映射到另一个平面上的点&#xff0c;这个变换矩阵是一…

java聊天室的设计与实现代码

聊天室是一个简单的通信应用&#xff0c;可以帮助您与客户和朋友保持联系&#xff0c;并且可以让您更轻松地与其他员工联系。然而&#xff0c;您将不得不确保每个人都知道他们正在做什么。 一旦聊天室开始&#xff0c;它就会变得非常复杂&#xff0c;因为有许多用户可能会同时登…

【三十天精通Vue 3】第二十五天 Vue3 与 Axios 后端数据交互

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 引言一、Vue3 与 Axios 概述二、Axios 安装与基本使用2.1 安装 Ax…

NECCS|全国大学生英语竞赛C类|词汇和语法|语法题|时态|22:30~11:44

15题 10min 10:20&#xff5e;10:25 test2 10:25&#xff5e;10:47 test1订正 10:44&#xff5e;11:47 理论学习 涉及的语法点主要包括&#xff1a; 动词的时态和语态 非谓语动词 虚拟语气 主谓一致 倒装句 强调句 比较级 名词性从句 定语…

【SQL篇】面试篇之子查询

1303 求团队人数 # 写法1 # Write your MySQL query statement below select employee_id, count(*) over(partition by team_id) as team_size from Employee# 写法2 # Write your MySQL query statement below select employee_id, team_size from Employee e join (select t…

优雅编程,从空格、空行、缩进、注释开始

很多初学者的代码其实都不够“漂亮”&#xff0c;那是因为没有养成好的编码习惯。本篇博客以C语言为例&#xff0c;总结一些好习惯。其实&#xff0c;很多习惯都是肌肉记忆&#xff0c;举个例子&#xff1a;请你写一个程序&#xff0c;输入2个整数并输出它们的和。有些朋友可能…

springboot+vue前后端分离项目打包成jar包及运行

将 Spring Boot 和 Vue.js 项目打包成 jar 包需要按照以下步骤操作&#xff1a; 在项目的根目录中&#xff0c;使用命令行进入 Vue.js 项目的根目录&#xff0c;然后运行以下命令&#xff1a; npm run build这个命令将会构建 Vue.js 项目&#xff0c;并在项目的 dist 目录中生…

Rust-Rocket框架笔记

Rust-Rocket框架笔记 Rocket-Learn-docRocket Addr视频地址 What is RocketQuickStart下载Rocket-Rust运行Rust-Rocket-Hello-错误-端口占用解决查看端口占用情况添加Rocket.toml配置文件更改Rocket默认启动端口启动成功 GetStart-Hello world创建项目cargoIDEA 添加依赖添加Ro…

使用eclipse创建一个图书管理系统(2)---------逻辑的实现

就像使用C语言写代码一样。比如要用​​​​​​C语言写一个小游戏的代码&#xff0c;我们的逻辑实现是在哪里实现的啊&#xff1f;是不是在一个test.c源文件里面啊&#xff1f;没错&#xff0c;就是的&#xff01;在之前的文章里&#xff0c;我说过我定义的Main函数就像C语言里…

【《中国工业经济》数据复现】数字化转型与企业分工:专业化还是纵向一体化

一.研究内容 本文使用机器学习方法刻画微观企业数字化水平&#xff0c;并在构建数理模型的基础上实证考察了企业数字化转型对企业分工的影响及其机理。结果表明&#xff0c;企业数字化转型显著提升了中国上市企业专业化分工水平。机制分析表明&#xff0c;数字化转型对企业专业…

实现chatgpt自然对话

1.概述 ChatGPT是当前自然语言处理领域的重要进展之一&#xff0c;通过预训练和微调的方式&#xff0c;ChatGPT可以生成高质量的文本&#xff0c;可应用于多种场景&#xff0c;如智能客服、聊天机器人、语音助手等。本文将详细介绍ChatGPT的原理、实战演练和流程图&#xff0c…

C/C++每日一练(20230503)

目录 1. 输出最长的递增数字字符串 &#x1f31f;&#x1f31f; 2. 缺失的第一个正数 &#x1f31f;&#x1f31f;&#x1f31f; 3. 最大矩形 &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日…