【数据结构】——双链表(增删查改)

news2024/11/25 9:42:19

 

目录

前言:

一:双链表的定义

​编辑 二:双向链表的实现

2.1:链表的构造

2.2:创建头节点

2.3:创建节点 

2.4:链表的尾插 

2.5:链表的打印

2.6:链表的尾删

2.7:链表的头插

2.8:链表的头删

2.9:链表的查找 

2.10:在目标位置前面插入

2.11:删除目标位置结点

2.12:链表的销毁

 总代码:

test.c

List.c

 List.h


 

前言:

双链表的引入是因为单链表要访问某个结点的前驱结点时,只能从头开始遍历,访问后驱结点的复杂度为O(1),访问前驱结点的复杂度为O(n)。为了克服上述缺点,引入了双链表。

双链表的引进,对于链表的操作有了极大的遍历;

一:双链表的定义

链表由单向的链变成了双向链。

双向链表(double linked list)是在单链表的每个结点中再设置一个指向其前驱结点的指针域。 

 二:双向链表的实现

2.1:链表的构造

包含了一个数据域,两个指针域(指向前后驱节点)

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;		//数据
	struct ListNode* next;	//下一个指针域
	struct ListNode* prev;	//上一个指针域
}ListNode;

 

2.2:创建头节点

双向链表一般都是带头节点的,在链表中,带了头节点对于链表分割这一问题有了简单化;

动态开辟出一块空间,前后指针都指向自己;

//初始化链表头头节点
ListNode* ListInit()
{
	 ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	 assert(phead);
	 phead->data = -1;
	 phead->next = phead;
	 phead->prev = phead;
	 return phead;
}

2.3:创建节点 

这里置NULL,和单链表的置NULL,是一样的意思,对后续的操作提供便利;

// 创建返回链表的结点.
ListNode* ListCreate(LTDataType x)
{
	 ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	 assert(newnode);
	 newnode->data = x;
	 newnode->next = NULL;
	 newnode->prev = NULL;
	 return newnode;
}

2.4:链表的尾插 

单链表的尾插还需要考虑是否存在第一个节点,这里直接插入即可;

注意操作顺序

// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* newnode = ListCreate(x);

	struct ListNode* tail = phead->prev;
	// phead tail  newnode

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

	//注意前后顺序
	newnode->prev = tail;
	tail->next = newnode;
	newnode->next = phead;
	phead->prev = newnode;

}

2.5:链表的打印

这里的关键就是从哪里开始?如何判断结束(因为是循环)?

我们可以从头结点的下一个开始打印,当遇到头结点即是结束;

// 双向链表打印
void ListPrint(ListNode* phead)
{
	assert(phead);
	struct ListNode* cur = phead->next;
	printf("哨兵位<=>");
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

 

2.6:链表的尾删

要多一个断言判断:如果只有一个头指针就不用操作了;

保存尾结点的前驱,释放尾结点即可

// 双向链表尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);	//只有头指针不删

	struct ListNode* cur = phead->prev;
	struct ListNode* curPrev = cur->prev;	//尾节点的上一个
	phead->prev = curPrev;
	curPrev->next = phead;
	free(cur);
	cur = NULL;
}

 

2.7:链表的头插

和尾插操作相似,定义头节点的下一个结点,进行链接即可;

注意顺序;

// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);

	ListNode* newnode = ListCreate(x);
	struct ListNode* cur = phead->next;

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

 

2.8:链表的头删

删除操作一般都需要判断一下是否只有头节点。判断双向链表的条件是:phead->next != phead;

// 双向链表头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);	//只有头指针不删

	ListNode* cur = phead->next;
	ListNode* next = cur->next;

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

	free(cur);
	cur = NULL;
}

2.9:链表的查找 

找到该结点后,返回的是指针,而不是数据,返回该指针位置,方便后续操作

// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

2.10:在目标位置前面插入

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);	//检查pos位置是否有效
	ListNode* newnode = ListCreate(x);

	newnode->next = pos;	//将newnode节点next prev 链接前后节点
	newnode->prev = pos->prev;
	pos->prev->next = newnode;
	pos->prev = newnode;
}

 

2.11:删除目标位置结点

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	
	ListNode* posPrev = pos->prev;
	ListNode* next = pos->next;

	posPrev->next = next;
	next->prev = posPrev;
	free(pos);
	pos = NULL;
}

 

2.12:链表的销毁

// 双向链表销毁
void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->prev;
	while (cur != phead)		//将除了头结点的都销毁
	{
		ListNode* curPrev = cur->prev;
		free(cur);
		cur = curPrev;
	}
	free(phead);    //再释放头结点
	//phead = NULL;
}

 总代码:

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"


void test1()
{
	ListNode* plist = NULL;
	plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPrint(plist);

	ListPopBack(plist);
	ListPrint(plist);

	ListPopBack(plist);
	ListPrint(plist);

	ListPopBack(plist);
	ListPrint(plist);

	ListPopBack(plist);
	ListPrint(plist);

}


void test2()
{
	ListNode* plist = NULL;
	plist = ListInit();

	ListPrint(plist);
	//ͷ
	ListPushFront(plist, 6);
	ListPrint(plist);

	//ͷɾ
	ListPopFront(plist);
	ListPrint(plist);

}

void test3()
{
	ListNode* plist = NULL;
	plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPrint(plist);
		
	//βɾ
	ListPopBack(plist);
	ListPopBack(plist);
	ListPopBack(plist);

	ListNode* pos = ListFind(plist,1);

	ListInsert(pos, 666);
	ListPrint(plist);

	ListErase(pos);
	ListPrint(plist);

	ListDestory(plist);
}
int main()
{
	//test1();
	//test2();
	test3();
	return 0;
}

List.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"List.h"


//初始化链表头头节点
ListNode* ListInit()
{
	 ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
	 assert(phead);
	 phead->data = -1;
	 phead->next = phead;
	 phead->prev = phead;
	 return phead;
}

// 创建返回链表的头结点.
ListNode* ListCreate(LTDataType x)
{
	 ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	 assert(newnode);
	 newnode->data = x;
	 newnode->next = NULL;
	 newnode->prev = NULL;
	 return newnode;
}

// 双向链表尾插
void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListNode* newnode = ListCreate(x);

	struct ListNode* tail = phead->prev;
	// phead tail  newnode

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

	//注意前后顺序
	newnode->prev = tail;
	tail->next = newnode;
	newnode->next = phead;
	phead->prev = newnode;

}

// 双向链表打印
void ListPrint(ListNode* phead)
{
	assert(phead);
	struct ListNode* cur = phead->next;
	printf("哨兵位<=>");
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

// 双向链表尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);	//只有头指针不删

	struct ListNode* cur = phead->prev;
	struct ListNode* curPrev = cur->prev;	//尾节点的上一个
	phead->prev = curPrev;
	curPrev->next = phead;
	free(cur);
	cur = NULL;
}

// 双向链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);

	ListNode* newnode = ListCreate(x);
	struct ListNode* cur = phead->next;

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

// 双向链表头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	assert(phead->next != phead);	//只有头指针不删

	ListNode* cur = phead->next;
	ListNode* next = cur->next;

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

	free(cur);
	cur = NULL;
}

// 双向链表查找
ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);
	
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos);	//检查pos位置是否有效
	ListNode* newnode = ListCreate(x);

	newnode->next = pos;	//将newnode节点next prev 链接前后节点
	newnode->prev = pos->prev;
	pos->prev->next = newnode;
	pos->prev = newnode;
}

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	
	ListNode* posPrev = pos->prev;
	ListNode* next = pos->next;

	posPrev->next = next;
	next->prev = posPrev;
	free(pos);
	pos = NULL;
}

// 双向链表销毁
void ListDestory(ListNode* phead)
{
	assert(phead);
	ListNode* cur = phead->prev;
	while (cur != phead)		//将除了头节点的都销毁
	{
		ListNode* curPrev = cur->prev;
		free(cur);
		cur = curPrev;
	}
	free(phead);
	//phead = NULL;
}

 List.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;		//数据
	struct ListNode* next;	//下一个指针域
	struct ListNode* prev;	//上一个指针域
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate(LTDataType x);

//初始化链表头头节点
ListNode* ListInit();

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);

// 双向链表打印
void ListPrint(ListNode* pHead);

// 双向链表尾删
void ListPopBack(ListNode* pHead);

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);

// 双向链表头删
void ListPopFront(ListNode* pHead);

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

// 双向链表销毁
void ListDestory(ListNode* pHead);

 以上就是我对【数据结构|双向链表|增删改查】的介绍,不足之处,还望指点。

 

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

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

相关文章

Abaqus 2022x新功能介绍第二弹

Abaqus 非线性力学的功能增强&#xff08;材料更新&#xff09; Valanis-Landel 超弹性材料 通过指定单轴试验数据和可选的体积试验数据&#xff08;v2022新增选项&#xff09;来定义Valanis-Landel 超弹性模型&#xff0c;该模型能精确地复现给定的数据&#xff0c;类似Marl…

在Ubuntu系统中安装VNC并结合内网穿透实现公网远程访问

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

CLIP浅谈

CLIP论文地址&#xff1a;Learning Transferable Visual Models From Natural Language Supervision CLIP代码地址&#xff1a;https://github.com/openai/CLIP 简介 CLIP是OpenAI在2021年2月发表的一篇文章&#xff0c;它的主要贡献有以下2点&#xff1a; 1&#xff09;将图…

【latex】公式推导等号对齐

使用aligned进行多行公式对齐&#xff0c;&作为对齐的节点&#xff0c;\作为公式换行 \begin{equation} \begin{aligned}a& bc \\& cd \end{aligned} \end{equation}

微信小程序云开发 微信支付功能 逻辑+踩坑

前置条件 首先开通微信支付商户号 然后小程序后台里关联商户号 然后在开发者工具里申请api权限 云开发》设置》其他设置》微信支付配置 申请一下权限和绑定 显示已通过即可。 逻辑 首先用户点击支付按钮&#xff0c;就会触发unlock() 在unlock函数中创建新订单&#xff…

UE5 - ArchvizExplorer - 数字孪生城市模板 - 功能修改

数字孪生项目&#xff0c;大多是双屏互动&#xff0c;而非下方菜单点击&#xff0c;所以要做一番改造 参考&#xff1a;https://blog.csdn.net/qq_17523181/article/details/133853099 1. 去掉提示框 打开BP_MasterMenu_Widget&#xff0c;进入EventGraph&#xff0c;断开Open…

【Docker】从零开始:1.Docker概述

【Docker】从零开始&#xff1a;1.Docker概述 1.什么是Docker2.为什么要使用Docker3.传统虚拟机技术与Linux容器技术的区别(1).传统虚拟机技术(2).Linux容器 4.Docker的特点一次构建、随处运行a.更快速的应用交付和部署b.更便捷的升级和扩缩容&#xff1a;c.更简单的系统运维d.…

windows环境搭建Zblog博客并发布上线公网可访问

文章目录 1. 前言2. Z-blog网站搭建2.1 XAMPP环境设置2.2 Z-blog安装2.3 Z-blog网页测试2.4 Cpolar安装和注册 3. 本地网页发布3.1. Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1. 前言 想要成为一个合格的技术宅或程序员&#xff0c;自己搭建网站制作网页是绕…

软件测试/测试开发丨人工智能的与软件测试完美结合

随着人工智能&#xff08;AI&#xff09;技术的不断发展&#xff0c;软件测试领域也在不断演变。结合ChatGPT、知识图谱、PyTorch深度学习框架以及视觉与图像识别自动化测试&#xff0c;我们探讨了软件测试在人工智能时代的前沿应用&#xff0c;以及如何通过这些创新技术提高软…

Vue3的7种和Vue2的12种组件通信

Vue3 组件通信方式 props$emitexpose / ref$attrsv-modelprovide / injectVuex Vue3 通信使用写法 props 用 props 传数据给子组件有两种方法&#xff0c;如下 方法一&#xff0c;混合写法 // Parent.vue 传送 <child :msg1"msg1" :msg2"msg2">…

公益SRC挖掘小技巧

目录 1、寻找漏洞 1)谷歌语法 2)fofa 2、挖掘漏洞 3、提交报告 第一步&#xff1a;“标题”和“厂商信息”和“所属域名” 第二步&#xff1a;其它内容 第三步&#xff1a;复现步骤 0、IP域名归属证明 1、漏洞页 2、该干啥 3、注入的结果 4、上榜吉时 时间&#x…

Git 笔记之gitignore

解释为&#xff1a;git ignore 即&#xff0c;此类型的文件将会被忽略掉&#xff0c;从而不会进行管理 具体的模板可以从 GitHub 网站上来进行设置 Common_gitignore: gitignoregithub开源项目&#xff0c;增加Kingdee开发内容 例如&#xff1a;Java模板&#xff1a; # Co…

纯CSS实现炫酷文本阴影效果

如图所示&#xff0c;这是一个文本阴影效果&#xff0c;阴影有多个颜色&#xff0c;鼠标悬停时文本阴影效果消失&#xff0c;文本回到正常的效果。让我们逐步分解代码&#xff0c;看看如何使用纯CSS实现这个效果的。 基于以上动图可以分析出以下是本次实现的主要几个功能点&am…

linux高级篇基础理论五(用户安全,口令设置,JR暴力破解用户密码,NMAP端口扫描)

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a; 小刘主页 ♥️不能因为人生的道路坎坷,就使自己的身躯变得弯曲;不能因为生活的历程漫长,就使求索的 脚步迟缓。 ♥️学习两年总结出的运维经验&#xff0c;以及思科模拟器全套网络实验教程。专栏&#xff1a;云计算技…

git修改commit历史提交时间、作者

1、修改最近的几条记录&#xff0c;进入提交记录列表&#xff0c;修改提交记录模式 git rebase -i HEAD~3 // 修改最近的三条记录&#xff0c;顺序排列按提交时间升序 指令说明&#xff1a; pick&#xff1a;保留该commit&#xff08;缩写:p&#xff09; reword&#xff1a…

linux上交叉编译qt库

linux上交叉编译qt库 Qt程序从X86平台Linux移植到ARM平台Linux需要做什么 1.在ubuntu上使用qt的源码交叉编译出Qt库 2.将编译好的库拷贝到开发板上并设置相应的环境变量&#xff08;库路径啥的&#xff09; 前两步一劳永逸&#xff0c;做一次就行 3.X86上写好程序代码&…

纯CSS动态渐变文本特效

如图所示&#xff0c;这是一个炫酷的文本渐变效果&#xff0c;如同冰岛的极光一般。本次的文章让我们逐步分解代码&#xff0c;了解其实现原理。 基于以上动图效果可以分析以下是本次动效实现的主要几点&#xff1a; 文本中有多个颜色的动画每个颜色显示的半径不同&#xff0…

适合上班族考的证书 - PMP证书

PMP证书是一种专业的项目管理证书&#xff0c;适合上班族考取。随着社会的发展和竞争的加剧&#xff0c;越来越多的企业开始注重项目管理能力的培养和提升。而PMP证书正是国际上公认的项目管理领域的权威认证&#xff0c;具有很高的知名度和影响力。 首先&#xff0c;PMP证书对…

成都优优聚美团代运营:塑造卓越优势,引领电商新时代

在当今这个数字化时代&#xff0c;美团作为中国最大的本地生活服务平台之一&#xff0c;正在为消费者和商家搭建一个无缝链接的桥梁。而在成都&#xff0c;有一家名为优优聚美团代运营的公司&#xff0c;他们凭借着专业的技能和高效的服务&#xff0c;成为了美团平台上的佼佼者…