【初阶数据结构】线性表之双链表

news2025/4/3 0:54:05

文章目录

目录

一、双链表的概念

二、双链表的实现

1.初始化

2.尾插

3.头插

4.打印

5.判断双链表是否为空

6.尾删

7.头删

8.查找

9.在指定的位置之后插入数据

10.删除指定位置的数据

11.销毁

三、完整源码

总结


一、双链表的概念

链表的结构非常多样,以下情况组合起来就有8种链表结构,分为带头,不带头,单向,双向,循环,不循环。

今天我们要了解的是带头双向循环链表,即双链表。

二、双链表的实现

双链表的定义

在实现双链表前,先简单了解三个指针:

  1. pcur:当前结点
  2. prev:指向当前结点的前一个结点,简称前驱结点
  3. next:指向当前结点的后一个结点,简称后继结点
//定义双链表结构体
typedef int LTDatatype;
typedef struct ListNode {
	LTDatatype data;
	struct ListNode* next;//指向下一个结点,即后继结点
	struct ListNode* prev;//指向上一个结点,即前驱结点
}LTNode;

1.初始化

代码解析:

初始化链表就需要开辟空间,在堆上申请一个结点的空间,用函数buyNode来进行实现,初始时让next,prev指向自己,并返回头结点

LTNode* buyNode(LTDatatype x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("NULL");
		exit(1);
	}
	node->data = x;
	node->next = node->prev = node;
	return node;
}

//初始化
LTNode* LTInit()
{
	LTNode* phead = buyNode(-1);
	return phead;
}

注:在双链表中,phead会自己指向自己,不会在指向下一个结点时而丢失,next、prev的指向的变化不会影响当前结点,即地址不会发生改变,所以实现双链表的函数都用一级指针。

2.尾插

代码解析:因为要新插入一个数据,所以开辟一个结点的空间(要插入几个结点,就开辟几个结点的空间)。先设置新结点,把新结点的位置插入到当前链表中,让newnode->prev指向尾结点(phead->prev)newnode->next指向头结点(phead);再更新原链表的指向,把尾结点(phead->prev)的next指向newnode头结点(phead)的prev指向newnode.

//尾插
void LTPushBack(LTNode* phead, LTDatatype x)
{
	assert(phead);
	LTNode* newnode = buyNode(x);
	
//phead phead->prev newnode

	newnode->prev = phead->prev;
	newnode->next = phead;

	phead->prev->next = newnode;
	phead->prev = newnode;
}

3.头插

代码解析:

和尾插一样需要开辟一个结点大小的空间。先设置新结点,把新结点的位置插入到当前链表中,让newnode->next指向d1(phead->next),newnode->prev指向头结点(phead);再改变链表中d1(phead->next)的位置,让d1->prev指向newnode,头结点(phead)的next指向newnode

//头插
void LTPushFront(LTNode* phead, LTDatatype x)
{
	assert(phead);
	LTNode* newnode = buyNode(x);
	newnode->next = phead->next;//phead->next为d1
	newnode->prev = phead;

	phead->next->prev = newnode;
	phead->next = newnode;
}

注:把newnode插在d1前面是头插,但插在头结点phead前面是尾插

4.打印

代码解析:通过遍历链表来打印结点

//打印
void LTPrint(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d ->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

5.判断双链表是否为空

代码解析:

bool函数判断头结点是否指向自己,是指向自己就为空

//bool类型判断
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

6.尾删

代码解析:

先判断链表是否为空,不为空就对最后一个结点进行删除。创建新指针del先保存当前结点(即尾结点phead->prev),避免变成野指针;让d2(del->prev)的next指向头结点,phead->prev指向d2->prev;最后对尾结点进行释放并置为NULL

//尾删
void LTPopBack(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->prev;
	//phead del->prev del
	del->prev->next = phead;
	phead->prev = del->prev;

	free(del);
	del = NULL;
}

7.头删

代码解析:

和尾删一样,先判断链表是否为空,不为空就对第一个结点进行删除。这里第一个结点不是头结点是d1。创建新指针del保存当前结点d1(phead->next),改变链表中的位置,让d2(del->next)的prev指向头结点,头结点的next指向d2(del->next)

//头删
void LTPopFront(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->next;
	//phead del del->next
	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}

8.查找

代码解析:

遍历链表中的结点,找到需要查找的结点就返回当前的结点,找不到就返回NULL

//查找
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
	LTNode* pcur = phead->next;
	while (pcur)
	{
		if (pcur->data = x)
		{
			return pcur;
		}
	}
	return NULL;
}

9.在指定的位置之后插入数据

代码解析:

在指定的位置之后插入数据之前,需要用函数LTFind对其进行查找,避免出错。和前面尾插 头插一样需要申请一个结点空间的大小。先设置新结点,把新结点的位置插入到当前链表中,比如在d2的后面插入一个结点,d2=pos,让newnode->next指向d2->nextnewnode->prev指向d2;再改变链表结点的位置,让d3(d2->next)的prev指向newnode,d3指向newnode.

void LTInsert(LTNode* pos, LTDatatype x)
{
	assert(pos);
	LTNode* newnode = buyNode(x);
	//pos newnode pos->next
	newnode->next = pos->next;
	newnode->prev = pos;

	pos->next->prev = newnode;
	pos->next = newnode;
}

10.删除指定位置的数据

代码解析:

先断言当前结点是否为空,不为空对其进行删除。比如删除d2,d2=pos,让d2->prev的next指向d2->nxet,在让d2->next的prev指向d2->prev.

void LTErase(LTNode* pos)
{
	assert(pos);
	//pos->prev pos pos->next
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;

	free(pos);
	pos = NULL;
}

11.销毁

代码解析:

创建新指针pcur,让pcur从phead->next开始销毁。循环遍历,又创建一个新指针next,让next从pcur->next开始走,保证next走在pcur前面,对下一个结点pcur->next进行保存,避免删除当前结点pcur时出现野指针的情况;保存好pcur->next就对当前pcur进行释放,让pcur继续往后走pcur=next,一直循环释放,只剩头结点就跳出循环。最后对头结点进行释放并置为空。

void LTDesTroy(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

三、完整源码

List.h

#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;
}LTNode;

//初始化
LTNode* LTInit();

//打印
void LTPrint(LTNode* phead);
//bool类型判断
bool LTEmpty(LTNode* phead);
//查找
LTNode* LTFind(LTNode* phead, LTDatatype x);

//尾插
void LTPushBack(LTNode* phead, LTDatatype x);

//头插
void LTPushFront(LTNode* phead, LTDatatype x);

//尾删
void LTPopBack(LTNode* phead);

//头删
void LTPopFront(LTNode* phead);

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDatatype x);

//删除pos位置数据
void LTErase(LTNode* pos);

//销毁
void LTDesTroy(LTNode* phead);

List.c

#include"List.h"

LTNode* buyNode(LTDatatype x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("NULL");
		exit(1);
	}
	node->data = x;
	node->next = node->prev = node;
	return node;
}

//初始化
LTNode* LTInit()
{
	LTNode* phead = buyNode(-1);
	return phead;
}

//打印
void LTPrint(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d ->", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

//bool类型判断
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}


//尾插
void LTPushBack(LTNode* phead, LTDatatype x)
{
	assert(phead);
	LTNode* newnode = buyNode(x);
	//phead phead->prev newnode
	newnode->prev = phead->prev;
	newnode->next = phead;

	phead->prev->next = newnode;
	phead->prev = newnode;
}

//头插
void LTPushFront(LTNode* phead, LTDatatype x)
{
	assert(phead);
	LTNode* newnode = buyNode(x);
	newnode->next = phead->next;//phead->next为d1
	newnode->prev = phead;

	phead->next->prev = newnode;
	phead->next = newnode;
}

//尾删
void LTPopBack(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->prev;
	//phead del->prev del
	del->prev->next = phead;
	phead->prev = del->prev;

	free(del);
	del = NULL;
}

//头删
void LTPopFront(LTNode* phead)
{
	assert(!LTEmpty(phead));
	LTNode* del = phead->next;
	//phead del del->next
	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}

//查找
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
	LTNode* pcur = phead->next;
	while (pcur)
	{
		if (pcur->data = x)
		{
			return pcur;
		}
	}
	return NULL;
}

//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDatatype x)
{
	assert(pos);
	LTNode* newnode = buyNode(x);
	//pos newnode pos->next
	newnode->next = pos->next;
	newnode->prev = pos;

	pos->next->prev = newnode;
	pos->next = newnode;
}

//删除pos位置数据
void LTErase(LTNode* pos)
{
	assert(pos);
	//pos->prev pos pos->next
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;

	free(pos);
	pos = NULL;
}

//销毁
void LTDesTroy(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

Test.c

#include"List.h"

void test()
{
	//LTNode* plist = NULL;
	//LTInit(&plist);

	//尾插
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPrint(plist);

	//头插
	//LTNode* plist = LTInit();
	//LTPushFront(plist, 1);
	//LTPushFront(plist, 2);
	//LTPushFront(plist, 3);
	//LTPushFront(plist, 4);
	//LTPrint(plist);

	//尾删
	//LTPopBack(plist);
	//LTPrint(plist);
	//LTPopBack(plist);
	//LTPrint(plist);
	//LTPopBack(plist);
	//LTPrint(plist);

	//头删
	//LTPopFront(plist);
	//LTPrint(plist);
	//LTPopFront(plist);
	//LTPrint(plist);
	//LTPopFront(plist);
	//LTPrint(plist);

	//在pos位置之后插入数据
	//LTNode* find = LTFind(plist, 4);
	//if (find == NULL)
	//{
	//	printf("无\n");
	//}
	//else {
	//	printf("有\n");
	//}
	//LTInsert(find, 77);
	//LTErase(find);
	//LTPrint(plist);

	//销毁
	LTDesTroy(plist);
	plist = NULL;


}

int main()
{
	test();
	return 0;
}


总结

非常感谢大家阅读完这篇博客。希望这篇文章能够为您带来一些有价值的信息和启示。如果您有任何问题或建议,欢迎在评论区留言,我们一起交流学习。

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

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

相关文章

智能路由系统-信息泄露漏洞挖掘

1.漏洞描述&#xff1a; Secnet-智能路由系统 actpt_5g.data 信息泄露&#xff0c;攻击者可利用此漏洞收集敏感信息&#xff0c;从而为下一步攻击做准备。 2.fofa搜索语句 title"安网-智能路由系统" || title"智能路由系统" || title"安网科技-智能…

表格图表切换,图表无法展示问题复盘

项目背景 103项目CPC卡使用模块在原有的表格展示数据的基础之上&#xff0c;增加环状饼图图表展示&#xff0c;采用tab切换的方式实现 问题描述 图表无法设置宽高&#xff0c;导致饼图无法渲染 具体代码 // 入口页<el-tabs type"card" class"cts_flex_t…

AI赋能python数据处理、分析与预测操作流程

以数据集预测鱼类种类(Species)开展以下研究。数据格式如下: 以下是一个系统的分析思路及推荐的机器学习算法: 1. 数据预处理与探索性分析 缺失值与异常值处理: 检查数据完整性(如Roach类中Weight=0的记录需修正或删除)。 通过箱线图或Z-Score检测异常值,判断是否需…

基于74LS192的十进制两位数正向计时器(proteus仿真)

在数字电路设计中&#xff0c;计时器是一个非常常见的应用。今天&#xff0c;我将分享一个基于 74LS192 双向计数器 的十进制两位数正向计时器电路设计。这个电路可以实现从 00 到 99 的十进制正向计数&#xff0c;并通过两个七段数码管显示结果。 最终效果如图&#xff1a; 各…

算法-前缀和与差分

一、前缀和&#xff08;Prefix Sum&#xff09; 1. 核心思想 前缀和是一种预处理数组的方法&#xff0c;通过预先计算并存储数组的前缀和&#xff0c;使得后续的区间和查询可以在**O(1)**时间内完成。 2. 定义 给定数组 nums&#xff0c;前缀和数组 prefixSum 的每个元素 p…

React(六)React过渡动画-CSS编写方式

React过渡动画 react-transition-group介绍 在开发中&#xff0c;我们想要给一个组件的显示和消失添加某种过渡动画&#xff0c;提高用户体验→可通过react-transition-group实现。React曾为开发者提供过动画插件 react-addons-css-transition-group&#xff0c;后由社区维护…

第十五章:Python的Pandas库详解及常见用法

在数据分析领域&#xff0c;Python的Pandas库是一个不可或缺的工具。它提供了高效的数据结构和数据分析工具&#xff0c;使得数据处理变得简单而直观。本文将详细介绍Pandas库的基本功能、常见用法&#xff0c;并通过示例代码演示如何使用Pandas进行数据处理。最后&#xff0c;…

libva基础

Libva&#xff08;Lib Video Acceleration&#xff09;是一个开源的库&#xff0c;实现了 **VA-API**&#xff08;Video Acceleration API&#xff09;&#xff0c;旨在为视频处理提供跨平台的硬件加速支持。 1、核心功能与作用 硬件加速抽象层&#xff1a;Libva 作为中间层&…

c++游戏开发第一期

以后我将要发c游戏开发的教程&#xff0c;可能更得比较慢。&#xff08;目测几个星期一更&#xff09;。 今天先讲个配置编译器。 我用的是Visual studio 2022和EasyX。 安装studio&#xff1a; 首先找到下载链接&#xff08;点我&#xff09;下拉找到下面图片的东西。 下完…

Elasticsearch:人工智能时代的公共部门数据治理

作者&#xff1a;来自 Elastic Darren Meiss 人工智能&#xff08;AI&#xff09;和生成式人工智能&#xff08;GenAI&#xff09;正在迅速改变公共部门&#xff0c;从理论探讨走向实际应用。正确的数据准备、管理和治理将在 GenAI 的成功实施中发挥关键作用。 我们最近举办了…

低功耗LPWAN模块开发指南:远距离无线通信与边缘计算融合实战‌

在远程资产追踪、野外环境监测等场景中&#xff0c;稳定可靠的长距离通信与超低功耗是系统设计的核心挑战。eFish-SBC-RK3576通过 ‌原生双UART接口 USB OTG扩展能力‌ &#xff0c;可无缝集成主流LPWAN模组&#xff08;LoRa/NB-IoT&#xff09;&#xff0c;实现“数据采集-边…

【超详细教程】2025年3月最新Pytorch安装教程(同时讲解安装CPU和GPU版本)

目录 一、前言二、pytorch简介三、安装准备工作3.1、下载Anaconda 四、判断是否有NVIDIA显卡五、安装pytorch-CPU版本六、安装pytorch-GPU版本6.1、查看CUDA显卡驱动版本6.2、安装CUDA6.3、安装CuDNN&#xff08;加速器&#xff09;6.4、安装pytorch-GPU6.5 其他方法安装注意 七…

虚拟电商-话费充值业务(二)话费充值对接供应商模块开发

一、对接供应商模块开发 供应商对接模块chongba_recharge_supplier主要负责的就是调用外部的供应商系统进行充值下单&#xff0c;这种调用是一种基于HTTP协议的调用。 此外在供应商对接模块中主要是实现的业务逻辑有&#xff1a; 1&#xff1a;余额或押金不足情况下的失败轮…

c#winform,倒鸭子字幕效果,typemonkey字幕效果,抖音瀑布流字幕效果

不废话 直接上效果图 C# winform 开发抖音的瀑布流字幕。 也是typemonkey插件字幕效果 或者咱再网上常说的倒鸭子字幕效果 主要功能 1&#xff0c;软件可以自定义添加字幕内容 2&#xff0c;软件可以添加字幕显示的时间区间 3&#xff0c;可以自定义字幕颜色&#xff0c;可以随…

游戏被外挂攻破?金融数据遭篡改?AI反作弊系统实战方案(代码+详细步骤)

一、背景与需求分析 随着游戏行业与金融领域的数字化进程加速,作弊行为(如游戏外挂、金融数据篡改)日益复杂化。传统基于规则的防御手段已难以应对新型攻击,而AI技术通过动态行为分析、异常检测等能力,为安全领域提供了革命性解决方案。本文以游戏反作弊系统和金融数据安…

晶晨S905L3A(B)-安卓9.0-开启ADB和ROOT-支持IPTV6-支持外置游戏系统-支持多种无线芯片-支持救砖-完美通刷线刷固件包

晶晨S905L3A(B)-安卓9.0-开启ADB和ROOT-支持IPTV6-支持外置游戏系统-支持多种无线芯片-支持救砖-完美通刷线刷固件包 适用型号&#xff1a;M401A、CM311-1a、CM311-1sa、B863AV3.1-M2、B863AV3.2-M、UNT403A、UNT413A、M411A、E900V22C、E900V22D、IP112H等等晶晨S905L3A(B)处…

AI来了,新手如何着手学习软件开发?

AI时代新手学习软件开发的7步进化指南 &#xff08;附具体工具与避坑策略&#xff09; 一、建立“人机协作”学习观 AI是教练&#xff0c;不是替身 正确姿势&#xff1a;用AI辅助理解概念&#xff08;如让DeepSeek 、ChatGPT用生活案例解释递归&#xff09;&#xff0c;但坚持手…

《K230 从熟悉到...》矩形检测

《K230 从熟悉到...》矩形检测 《庐山派 K230 从熟悉到...》矩形检测 矩形检测技术是一种广泛应用于电子图像处理的核心技术。它通过识别和分析图像中的矩形结构&#xff0c;为各种应用提供基础支持。从传统图像处理算法到现代深度学习技术&#xff0c;矩形检测的实现途径多种多…

3. 第三放平台部署deepseek

有时候我们会发现使用deepseek服务器&#xff0c;异常卡顿&#xff0c;这是由于多方面原因造成的&#xff0c;比如说访问人数过多等。想要解决这个问题&#xff0c;我们可以选择第三方平台进行部署 第三方平台 我们可以选择的第三方平台很多&#xff0c;比如硅基流动、秘塔搜索…

【C++指针】搭建起程序与内存深度交互的桥梁(下)

&#x1f525;&#x1f525; 个人主页 点击&#x1f525;&#x1f525; 每文一诗 &#x1f4aa;&#x1f3fc; 往者不可谏&#xff0c;来者犹可追——《论语微子篇》 译文&#xff1a;过去的事情已经无法挽回&#xff0c;未来的岁月还可以迎头赶上。 目录 C内存模型 new与…