C语言:双链表

news2024/11/24 1:33:42

一、什么是双链表?

双链表,顾名思义,是一种每个节点都包含两个链接的链表:一个指向下一个节点,另一个指向前一个节点。这种结构使得双链表在遍历、插入和删除操作上都表现出色。与单链表相比,双链表不仅可以从头节点开始遍历,还可以从尾节点开始遍历,甚至从中间某个节点开始双向遍历。

二、双链表的特点

双向性:每个节点都包含两个指针,一个指向前一个节点,一个指向后一个节点。这使得双链表在遍历上更加灵活。
动态性:链表的大小可以根据需要动态地增加或减少,无需预先分配固定大小的内存空间。
三、实现双链表

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LTNode;

三、实现的功能

LTNode* LTInit();// 初始化双链表
void LTDestroy(LTNode* phead);//销毁
void LTPrint(LTNode* phead);//打印
bool LTEmpty(LTNode* phead);//判断链表是否为空·

void LTPushBack(LTNode* phead, LTDataType x);//尾插
void LTPopBack(LTNode* phead);//尾删

void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPopFront(LTNode* phead);//头删
//在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);//指定删除
LTNode* LTFind(LTNode* phead, LTDataType x);//查找

 1.创建节点

// 创建新的双链表节点  
LTNode* LTBuyNode(LTDataType x) {  
    LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));  
    if (newNode == NULL) {  
        perror("malloc fail!");  
        exit(1);  
    }  
    newNode->data = x;  
    newNode->next = NULL;  
    newNode->prev = NULL;  
    return newNode;  
}  
  

使用malloc函数在堆上动态地分配内存空间,以存储LTNode结构体的大小。
检查malloc是否成功分配了内存。如果返回NULL,表示内存分配失败,此时调用perror函数打印错误消息,并使用exit(1)退出程序。
如果内存分配成功,将新节点的数据成员data设置为参数x的值。
初始化新节点的next和prev指针为NULL,表示这个新节点在创建时并不指向任何其他的节点。
返回指向新创建节点的指针。

2.初始化

LTNode* LTInit() {  
    LTNode* pheda = LTBuyNode(-1); // 使用-1作为哨兵位头节点的数据  
    pheda->next = pheda; // 指向自己,表示链表为空  
    pheda->prev = pheda;  
    return pheda;  
}

 3.双链表的尾插

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);// 创建一个新节点
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev->next = newnode;
	phead->prev = newnode;

}

newnode->prev = phead->prev; 将新节点的prev指针设置为当前链表的尾节点。
newnode->next = phead; 将新节点的next指针设置为头节点。

phead->prev->next = newnode; 更新当前尾节点的next指针,使其指向新节点。
phead->prev = newnode; 更新头节点的prev指针,使其指向新节点

4.双链表的尾删

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

prev指针指向链表的最后一个节点

del->prev->next = phead; 和 phead->prev = del->prev; 这两行代码更新了链表的链接,将尾节点从链表中移除。

 5.双链表的头插

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode ->next = phead->next;
	newnode->prev = phead;
	phead->next->prev = newnode;
	phead->next = newnode;
}

newnode ->next = phead->next;将新节点的next指针指向当前链表的第一个节点

newnode->prev = phead;将新节点的prev指针指向链表的头节点

phead->next->prev = newnode;更新当前链表第一个节点的prev指针,使其指向新节点。

phead->next = newnode;:更新链表的头节点的next指针,使其指向新节点,这样新节点就成为了链表的第一个节点。

6.双链表的头删

void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->next;
	phead->next = del->next;
	phead->next->prev = phead;
	free(del);
	del = NULL;

}

 LTNode* del = phead->next;:将待删除的节点的地址赋给del指针。
phead->next = del->next;:更新链表的头节点的next指针,使其跳过待删除的节点,直接指向下一个节点。
phead->next->prev = phead;:由于我们刚刚更新了phead->next,现在它指向的是原第一个节点的下一个节点。我们将这个新节点的prev指针更新为指向链表的头节点。

7.双链表的打印 

void LTPrint(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d ", pcur->data);
		pcur = pcur->next;

	}
	printf("\n");
}

8.在pos位置之后插入数据

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);	

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

9.指定删除     

void LTErase(LTNode* pos)
{
	assert(pos);
	assert(pos != pos->next);
	pos->prev->next = pos->next;// 更新pos的前一个节点的next指针
	pos->next->prev = pos->prev;//更新pos的下一个节点的prev指针
	free(pos);
	pos = NULL;
}

 10.判空

bool LTEmpty(LTNode* phead)
{
	return phead->next == phead;
}

 11.销毁

//销毁
void LTDestroy(LTNode* phead)
{
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* tmp = cur;
		cur = cur->next;
		free(tmp);
	}
	free(phead);
}

四、全部源码

LTNode* LTBuyNode(LTDataType x)
{
	LTNode* Node = (LTNode*)malloc(sizeof(LTNode));
	if (Node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	Node->data = x;
	Node->next = NULL;  
	Node->prev = NULL;   
	return Node;
}
LTNode* LTInit() {
	LTNode* pheda = LTBuyNode(-1); // 使用-1作为哨兵头节点的数据  
	pheda->next = pheda; // 指向自己,表示链表为空  
	pheda->prev = pheda;  
	return pheda;
}

//销毁
void LTDestroy(LTNode* phead)
{
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* tmp = cur;
		cur = cur->next;
		free(tmp);
	}
	free(phead);
}
//打印
void LTPrint(LTNode* phead)
{
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d ", pcur->data);
		pcur = pcur->next;

	}
	printf("\n");
}
//判断链表是否为空·
bool LTEmpty(LTNode* phead)
{
	return phead->next == phead;
}
//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode->prev = phead->prev;
	newnode->next = phead;
	phead->prev->next = newnode;
	phead->prev = newnode;

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

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	newnode ->next = phead->next;
	newnode->prev = phead;
	phead->next->prev = newnode;
	phead->next = newnode;
}
//头删
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->next;
	phead->next = del->next;
	phead->next->prev = phead;
	free(del);
	del = NULL;

}
//查找

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur=cur->next;

	}
	return NULL;
}

	在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);	

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

//指定删除
void LTErase(LTNode* pos)
{
	assert(pos);
	assert(pos != pos->next);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

五、结语 

让我们一起在编程的道路上不断前行,创造更加美好的未来!

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

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

相关文章

C++基础编程100题-008 OpenJudge-1.3-06 甲流疫情死亡率

更多资源请关注纽扣编程微信公众号 http://noi.openjudge.cn/ch0103/06/ 描述 甲流并不可怕,在中国,它的死亡率并不是很高。请根据截止2009年12月22日各省报告的甲流确诊数和死亡数,计算甲流在各省的死亡率。 输入 输入仅一行&#xff…

基于NodeJs 的Vue安装和创建项目

基于NodeJs 的Vue安装和创建项目 一、Node.js的下载与安装 下载地址: https://nodejs.org/en/download/prebuilt-installer 安装完之后,启动 cmd命令行,验证 Node.js 是否安装成功 二、配置npm的全局模块的存放路径以及缓存的路径 注&…

[数据集][图像分类]人种黄种人白人黑人等分类数据集56000张7类别

数据集类型:图像分类用,不可用于目标检测无标注文件 数据集格式:仅仅包含jpg图片,每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数):56000 分类类别数:7 类别名称:[“Black”,“East_Asian”,“Ind…

Meta首席AI科学家Yann LeCun指出生成式AI的不足

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

华为防火墙 1

华为防火墙1 实验拓扑: 实验步骤: 1.完成终端基本IP信息配置 2.配置防火墙: 2.1配置IP地址 sys Enter system view, return user view with CtrlZ. [USG6000V1]undo in e Info: Saving log files… Info: Information center is disabled. […

爬虫——有道云翻译

废话不多说直接上代码 固定文本内容 import timefrom selenium import webdriver from selenium.common.exceptions import NoSuchElementException, TimeoutException from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWai…

debian12安装时分区方案

一、初次尝试 一共设置了4个分区,其中根目录/分区46G,swap分区10G(电脑内存为6G),/boot分区200M,/home分区55G。系统安装之后的实际占有情况为: 二、调整后情况 一共设置了4个分区&#xff0c…

[vulnhub]Lin.Security主机Linux提权

Hash Crack(Hash cat) boblinsecurity:~$ cat /etc/passwd $ echo "AzER3pBZh6WZE">hash 检查哈希类型: $ hash-identifier AzER3pBZh6WZE $ hashcat -m 1500 -a 0 hash /usr/share/wordlists/rockyou.txt --force username:insecurity password:AzER3pBZh6WZE…

TransformerFAM:革新深度学习的新型注意力机制

深度学习领域的一项突破性技术——Transformer架构,已经彻底改变了我们处理序列数据的方式。然而,Transformer在处理长序列数据时面临的二次复杂度问题,限制了其在某些应用场景下的潜力。针对这一挑战,研究者们提出了一种名为Tran…

sqlmap直接嗦 dnslog注入 sqllibs第8关

dnslog注入是解决注入的时候没有回显的情况,通过dns外带来进行得到我们想要的数据。 我们是用了dns解析的时候会留下记录,这时候就可以看见我们想要的内容。 这个时候我们还要了解unc路径以及一个函数load_file()以及concat来进行注入。看看我的笔记 unc…

每日5题Day19 - LeetCode 91 - 95

每一步向前都是向自己的梦想更近一步,坚持不懈,勇往直前! 第一题:91. 解码方法 - 力扣(LeetCode) class Solution {public int numDecodings(String s) {int n s.length();//注意我们dp的范围是n1int[] d…

解决PyQt5中柱状图上显示的数值为带e的科学计数法

PyQt5生成柱状图的代码参考:PyQt5 QtChart-柱状图 参照上述文章,生成柱状图后,数值较大或较小情况下会导致柱状图上显示数值为带e的科学计数法,这样会影响数值的识别: 经过分析QBarSet方法得到解决方法:需…

车载电子电气架构 --- 车载信息安全

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

先进封装技术的一些优缺点探讨

半导体封装技术是半导体制造过程中的关键环节,它不仅保护了芯片免受物理损伤,还提供了电气连接和散热功能。随着技术的发展,出现了多种先进的封装技术,每种技术都有其特定的应用场景和优缺点。 --> 1. 传统封装技术 【优点】&…

memory动态内存管理学习之unique_ptr

此头文件是动态内存管理库的一部分。std::unique_ptr 是一种智能指针,它通过指针持有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象。在发生下列两者之一时,用关联的删除器释放对象: 管理它的 unique_ptr 对象被销毁。…

五款软件推荐:U盘数据不小心删除了?帮你快速找回!

U盘数据不小心删除了怎么恢复?U盘是一种便携式存储设备,因其小巧轻便而广受欢迎。但是,U盘也常常会遇到数据丢失的问题。当U盘数据丢失时,需要找到一款可靠的数据恢复软件来恢复数据。 接下来为大家推荐5款好用的免费U盘数据恢复软…

SmartDraw Suite Edition 画图软件画表格内存示意图的使用方法

总述:遇到不会画的,比如如何画一条虚线,先将 虚线 翻译成英文,然后在 help 中查询。 新建的时候选择如下: 一、选择 Forms->Blank Form 二、画表格: 三、画箭头:先选择1在选择2 四、编辑文…

基于睡眠声音评估睡眠质量

随着健康意识的增强,人们越来越关注睡眠质量。确保获得充足的高质量睡眠对于维持身体健康和心理平衡至关重要。专业的睡眠状态测量主要通过多导睡眠图(PSG)进行。然而,PSG会给受试者带来显著的身体负担,并且在没有专业…

力扣hot100学习记录(十二)

94. 二叉树的中序遍历 给定一个二叉树的根节点 root,返回它的中序遍历。 题意 给定一个二叉树,返回它的中序遍历 思路 采用递归的思想,只要根节点不为空,则一直递归遍历左子树,然后将根节点的值存入结果,…

深度学习中几种常见数据标准化方法

目录 一、介绍 二、总结 三、详情 1. StandardScaler 2. MinMaxScaler 3. RobustScaler 4. MaxAbsScaler 5. Normalizer 6. QuantileTransformer 7. PowerTransformer 8. Log Transform 四、示例 五、心得 一、介绍 方法名称缩放范围适用条件StandardScaler均值…