备战蓝桥杯 链表详解

news2025/1/11 18:45:37

目录

链表概念

静态单链表的实现

静态双链表的实现

循环链表

算法题练习:

1.排队顺序

2.单向链表

3.队列安排

4.约瑟夫问题


链表概念

上一次我们用顺序存储实现了线性表,这次我们用链式存储结构实现的线性表就叫链表

链表每个节点包含数据本身和下一个节点和上一个节点的地址

链表的分类

单链表 双链表

带头链表 不带头链表

循环链表等等

我们竞赛一般都用的是带头链表

双向链表的特点是比较任意的找到前驱节点

循环链表的特点是从任意节点的位置开始都能遍历完整个链表

我们的动态实现链表是用new申请节点,delete释放节点

但是这种动态形式对时间消耗很大,所以我们竞赛中实现的是静态的链表

静态单链表的实现

我们用两个足够大的数组来实现静态链表

一个是elem数组用来存储每个节点的数据域

一个是next数组用来存储每个节点的指针域

一个变量h表示头节点的位置

一个变量id表示新加入节点的位置

静态单链表的头插

我们应该先把2连上,再进行1的连接,如果我们先进行1的连接,那么第一个节点就找不到了,2无法进行

所以我们要头插的时候,先让id++ 为新节点腾位置,再把数据的值给e[id]

然后我们实现2号连接,我们把ne[id]=ne[h]

最后我们实现1号连接,ne[h]=id;

我们的时间复杂度就是O(1)

代码:

void push_front(int x)
{
	e[++id] = x;
	ne[id] = ne[h];
	ne[h] = id;
}

遍历链表:

用for循环,int i先初始化为ne[h],i只要不等于0就继续进入循环,每次循环i都变成ne[i] 就完成了我们的遍历链表操作

代码

void Print()
{
	for (int i = ne[h]; i; i = ne[i])
	{
		cout << e[i] << " ";
	}
	cout << endl;
}

测试结果

查询节点 第一种方法就是遍历链表查询,返回节点的下标

int find(int x)
{
	//解法1遍历链表
	for (int i = ne[h]; i; i = ne[i])
	{
		if (e[i] == x)
			return i;
	}
	return 0;
}

查询节点的第二种方法,用空间替代时间,开一个mp数组,mp数组的下标就是我们链表的值,数组的值就是我们的链表节点的存储位置

相比于遍历的查询,我们mp数组的时间复杂度只是O(1),但是有两点局限性,1是数据的值不能太大,2是数据的值不能重复,不然就不知道存哪个下标了

int find(int x)
{
	return mp[x];
}

每次头插的时候 把新节点的存储位置更新到mp里

void push_front(int x)
{
	e[++id] = x;
	mp[x] = id;
	ne[id] = ne[h];
	ne[h] = id;
}

在任意位置之后 插入节点

如果我们先连接1号路线,那我们就找不到三这个节点了,所以我们应该先连接2号路线,也就是说,我们先让id++给新节点腾出位置,然后我们连接2号路线也就是让ne[id]=ne[p],再连接1号路线,也就是让ne[p]=id

void insert(int p, int x)
{
	//在存储位置为p的节点后面插入一个新节点
	id++;
	e[id] = x;
	mp[x] = id;
	ne[id] = ne[p];
	ne[p] = id;
	
}

删除在任意位置之后的元素

如图,这种情况我们只要让1直接连接3 跳过2就行了,这时候虽然2还在我们的数组里面,但是遍历的时候不会遍历到他,也就相当于删除了这个节点了

也就是我们要删除2,就让ne[1]=ne[ne[1]]就行了,如果我们传p的话,就让ne[p]=ne[ne[p]]

当然,如果我们删除的节点是最后一个的下一个的话,就需要特殊判断一下,不然会存在bug

比如下图

我们要删除6节点的下一个节点的时候,ne[6]=ne[ne[6]],也就是ne[6]=ne[0],就会让我们的6节点和4节点再形成一个连接,破坏原有的结构

所以我们需要特判一下

void erase(int p)
{
	if (ne[p])//当p不是最后一个元素的时候
	{
		mp[e[ne[p]]] = 0;//清空标记
		ne[p] = ne[ne[p]];

	}
}

测试

我们单链表会了头插,查询存储位置,任意位置之后插入,任意位置之后删除这几个操作就够用了,其他的操作时间复杂度太高了我们基本用不到

静态单链表总代码

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int e[N], ne[N], h, id;
int mp[N];
void Print()
{
	for (int i = ne[h]; i; i = ne[i])
	{
		cout << e[i] << " ";
	}
	cout << endl;
}
void push_front(int x)
{
	e[++id] = x;
	mp[x] = id;
	ne[id] = ne[h];
	ne[h] = id;
}
int find(int x)
{
	解法1遍历链表
	//for (int i = ne[h]; i; i = ne[i])
	//{
	//	if (e[i] == x)
	//		return i;
	//}
	//return 0;
	return mp[x];
}
void insert(int p, int x)
{
	//在存储位置为p的节点后面插入一个新节点
	id++;
	e[id] = x;
	mp[x] = id;
	ne[id] = ne[p];
	ne[p] = id;
	
}
void erase(int p)
{
	if (ne[p])//当p不是最后一个元素的时候
	{
		mp[e[ne[p]]] = 0;//清空标记
		ne[p] = ne[ne[p]];

	}
}
int main()
{
	for (int i = 1; i <= 5; i++)
	{
		push_front(i);
		Print();
	}
	erase(2);
	Print();
	erase(3);
	Print();
	/*cout << find(1) << endl;
	cout << find(5) << endl;
	cout << find(6) << endl;*/
	/*insert(3, 10);
	insert(5, 100);
	Print();*/
}

静态双链表的实现

下面我们来介绍一下双链表的实现,双链表无非就是在单链表的基础上增加了前驱指针,我们只需要多开一个足够大的数组来存储前面的元素的存储位置就行了

静态双链表的创建

#include <iostream>
using namespace std;
const int N = 1e5+10;
int e[N], ne[N], pre[N];
int id, h;

静态双链表的头插

a是哨兵位

和单链表一样,我们1号路线应该最后实现不然的话我们就找不到b这个节点了,自然也就连不上我们的链表了,我们先让id++为新节点腾出位置,然后e[id]=x 为了规范一下操作

我们首先先把新节点的pre指针和ne指针与相邻的两个节点连接

也就是ne[id]=ne[h] , pre[id] = h

接下来我们把b节点的前指针连接新节点,也就是3号路线

pre[ne[h]]=id

最后,我们修改哨兵位的ne指针,

ne[h]=id;

我们来实现一下代码

void push_front(int x)
{
	id++;
	e[id] = x;
	//先修改新来节点的左右指针
	ne[id] = ne[h];
	pre[id] = h;
	//再修改哨兵位下一个节点的左指针
	pre[ne[h]] = id;
	//最后修改哨兵位的右指针
	ne[h] = id;
}

测试头插

实现按值查找,我们还是用mp[N]空间代替时间

int find(int x)
{
	return mp[x];
}

在插入的时候更新mp数组

在任意位置之后插入元素

如图,我们想要在p这个存储位置后面插入一个元素,和头插差不多,id++,e[id]=x我们应该先更改新节点的左右指针,也就是pre[id] = p ne[id] = ne[p]

然后更改p右边的节点的左指针

pre[ne[p]]=id

最后更改p位置的右指针

ne[p]=id

代码实现

void insert_back(int p, int x)
{
	id++;
	e[id] = x;
	//修改新节点左右指针
	ne[id] = ne[p];
	pre[id] = p;

	//修改p后面节点的左指针
	pre[ne[p]] = id;
	//修改p的右指针
	ne[p] = id;
}

测试

在任意位置之前插入元素

比如如图,我们要插入y这个节点,首先给节点腾出位置,然后先把新节点的左右指针连接上,然后连插入节点之前的节点的右指针,最后修改p的左指针。

代码

void insert_front(int p, int x)
{
	id++;
	e[id] = x;
	//修改新节点的左右指针
	ne[id] = p;
	pre[id] = pre[p];
	//修改p前节点的右指针
	ne[pre[p]] = id;
	//修改p存储位置的左指针
	pre[p] = id;
	mp[x] = id;
}

测试代码

删除任意位置的元素

如图我们要删除p存储位置的节点,首先我们要让ne[pre[p]] = ne[p]

然后让  pre[ne[p]] = pre[p]

也就是让该节点的上一个节点的右指针指向该节点下一个节点,让该节点下一个节点的左指针指向该节点的上一个指针,然后mp[e[p]] = 0清空标记

循环链表

我们之前写的单链表其实就是循环链表,因为我们把最后一个节点的右指针写为0,其实就是头节点的下标

算法题练习:

1.排队顺序

第一行n是小朋友个数

第二行分别是第i个小朋友的下一个小朋友的编号,相当于我们链表的ne[N]的数组,而我们的数据域就是下标,我们只需要写一个遍历链表的代码就行了

第三行表示第一个小朋友的编号,我们遍历的起点

#include <iostream>
using namespace std;
const int N = 1e6+10;
int ne[N];
int n;
int main()
{
	cin >> n;
	for(int i = 1;i<=n;i++)
	{
		cin >> ne[i];
	}
	int h;
	cin >> h;
	for(int i = h;i;i=ne[i])
	{
		cout << i << " ";
	 } 
}

2.单向链表

#include <iostream>
using namespace std;
const int N = 1e5+10;
const int M = 1e6+10;
int id,h,ne[N],e[N];
int mp[M];

int main()
{
	id++;
   e[id] = 1;
   mp[1] = id;
	int q;
	cin >> q;
	int op,x,p;
	while(q--)
	{
		cin >> op >> x;
		p = mp[x];
		if(op == 1)//插入到x后面 
		{
			int y;
			cin >> y;
			id++;
			e[id] = y;
			ne[id] = ne[p];
			ne[p] = id;
			mp[y] = id;	
		}
		else if(op == 2)
		{
			if(ne[p])
			{
				cout << e[ne[p]] << endl;	
			}
			else cout << "0" << endl;
		}
		else
		{
			mp[e[ne[p]]] = 0;
			ne[p] = ne[ne[p]];	
				}
	}
}

3.队列安排

4.约瑟夫问题

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

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

相关文章

灵活运用事务回滚,快捷处理多张数据表格

各位编程宝子们&#xff08;尤其是对MySQL了解不多的宝子们&#xff09;在使用关系表处理时&#xff0c;有时候会希望简单一次性解决多张表的数据处理&#xff0c;但又有时候无从下手。其实有时候掌握数据的事务和回滚便可以简单解决这些事情&#xff0c;接下来我将以一个学生信…

使用C# CEFSharp在WPF中开发桌面程序实现同一网站多开功能

在网络商业运营领域&#xff0c;同时运营多个淘宝店铺的现象屡见不鲜。为了满足这一需求&#xff0c;实现同一网址的多开功能变得尤为关键。这一需求虽然实用&#xff0c;但实现起来却面临诸多挑战。在这个过程中&#xff0c;技术人员们也经历了不少喜怒哀乐。 开发经历回顾 …

CompletableFuture // todo

相比较所有代码都在主线程执行&#xff0c;使用Future的好处&#xff1a;利用服务器多核、并发的优势。 不足&#xff1a; 开启没有返回值的异步线程&#xff1a; 1、runAsync 使用lambda表达式&#xff1a; 开启有返回值的异步线程&#xff1a; 1、supplyAsync 异步任务中的…

如何评价deepseek-V3 VS OpenAI o1 自然语言处理成Sql的能力

DeepSeek-V3 介绍 在目前大模型主流榜单中&#xff0c;DeepSeek-V3 在开源模型中位列榜首&#xff0c;与世界上最先进的闭源模型不分伯仲。 准备工作&#xff1a; 笔者只演示实例o1 VS DeepSeek-V3两个模型&#xff0c;大家可以自行验证结果或者实验更多场景&#xff0c;同时…

ASP.NET Core 实现微服务 - Consul 配置中心

这一次我们继续介绍微服务相关组件配置中心的使用方法。本来打算介绍下携程开源的重型配置中心框架 apollo 但是体系实在是太过于庞大&#xff0c;还是让我爱不起来。因为前面我们已经介绍了使用Consul 做为服务注册发现的组件 &#xff0c;那么干脆继续使用 Consul 来作为配置…

tdengine数据库使用java连接

1 首先给你的项目添加依赖 <dependency> <groupId>com.taosdata.jdbc</groupId> <artifactId>taos-jdbcdriver</artifactId> <version>3.4.0</version> <!-- 表示依赖不会传递 --> </dependency> 注意&am…

深入学习RabbitMQ的Direct Exchange(直连交换机)

RabbitMQ作为一种高性能的消息中间件&#xff0c;在分布式系统中扮演着重要角色。它提供了多种消息传递模式&#xff0c;其中Direct Exchange&#xff08;直连交换机&#xff09;是最基础且常用的一种。本文将深入介绍Direct Exchange的原理、应用场景、配置方法以及实践案例&a…

51单片机——串口通信(重点)

1、通信 通信的方式可以分为多种&#xff0c;按照数据传送方式可分为串行通信和并行通信&#xff1b; 按照通信的数据同步方式&#xff0c;可分为异步通信和同步通信&#xff1b; 按照数据的传输方向又可分为单工、半双工和全双工通信 1.1 通信速率 衡量通信性能的一个非常…

本地手集博客id“升级”在线抓取——简陋版——(2024年终总结1.1)

我之前每每发布笔记都用csv纯文本记录&#xff0c;一个机缘巧得文章列表api实现在线整理自已的文章阅读量数据。 (笔记模板由python脚本于2025年01月10日 18:48:25创建&#xff0c;本篇笔记适合喜欢钻牛角尖的coder翻阅) 【学习的细节是欢悦的历程】 Python官网&#xff1a;htt…

高等数学学习笔记 ☞ 洛必达法则与泰勒公式

1. 洛必达法则 1. 型与型未定式&#xff08;洛必达法则&#xff09; &#xff08;1&#xff09;型&#xff1a;若函数同时满足以下条件&#xff1a; &#xff08;2&#xff09;型&#xff1a;若函数同时满足以下条件&#xff1a; ①&#xff1a;当时&…

Qt官方下载地址

1. 最新版本 Qt官方最新版本下载地址&#xff1a;https://www.qt.io/download-qt-installer 当前最新版本Qt6.8.* 如下图&#xff1a; 2. 历史版本 如果你要下载历史版本安装工具或者源码编译方式安装&#xff0c;请转至此链接进行下载&#xff1a;https://download.qt.i…

怎么用NodeJS脚本对接TTS播报音响

怎么用NodeJS脚本对接TTS播报音响呢&#xff1f; 本文描述了使用NodeJS脚本调用HTTP接口&#xff0c;对接TTS播报音响&#xff0c;。 可选用产品&#xff1a;可根据实际场景需求&#xff0c;选择对应的规格 序号设备名称厂商1智能语音音柱|10W统软物联2智能语音壁挂音箱|款式…

计算机存储之图解机械硬盘

问&#xff1a;机械硬盘是如何工作的&#xff1f; 答&#xff1a;请看VCR 一、机械硬盘物理结构 1.1、盘片(platter) 视频中银白色的圆盘称为盘片&#xff0c;二进制数据就是存储在盘片上&#xff0c;盘片解剖后如下图所示&#xff1a; 一圈一圈的同心圆称为磁道&#xff08;…

HTML前端从零开始

第一天 HTML部分 什么是HTML HTML&#xff08;Hypertext Markup Language&#xff09;超文本标记语言。HTML是万维网的基石。 超&#xff1a; 超字第一层意义是指最重要的标签&#xff0c;超链接标签 超越文本的意思 HTML的发展历程 HTML1990年出现&#xff0c;web之父…

moviepy 将mp4视频文件提取音频mp3 - python 实现

DataBall 助力快速掌握数据集的信息和使用方式&#xff0c;会员享有 百种数据集&#xff0c;持续增加中。 需要更多数据资源和技术解决方案&#xff0c;知识星球&#xff1a; “DataBall - X 数据球(free)” -------------------------------------------------------------…

【Leetcode-移动零】利用将非零元素移动至数组前解决移动零问题(剪枝优化)

题目描述 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例1 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例2 输入: nums [0] 输出…

浅析大语言模型安全和隐私保护国内外标准和政策

过去两年&#xff0c;大模型技术已经普及并逐步渗透到各行各业&#xff0c;2025年注定是大模型应用井喷式发展的一年&#xff0c;AI在快速发展的同时&#xff0c;其带来的安全风险也逐渐凸显。人工智能系统的安全性和隐私保护已经成为社会关注的重点。 附下载&#xff1a;600多…

第21篇 基于ARM A9处理器用汇编语言实现中断<三>

Q&#xff1a;怎样编写ARM A9处理器汇编语言代码配置按键端口产生中断&#xff1f; A&#xff1a;使用Intel Monitor Program创建中断程序时&#xff0c;Linker Section Presets下拉菜单中需选择Exceptions。主程序在.vectors代码段为ARM处理器设置异常向量表&#xff0c;在…

直流无刷电机控制(FOC):电流模式

目录 概述 1 系统框架结构 1.1 硬件模块介绍 1.2 硬件实物图 1.3 引脚接口定义 2 代码实现 2.1 软件架构 2.2 电流检测函数 3 电流环功能实现 3.1 代码实现 3.2 测试代码实现 4 测试 概述 本文主要介绍基于DengFOC的库函数&#xff0c;实现直流无刷电机控制&#x…

(四)结合代码初步理解帧缓存(Frame Buffer)概念

帧缓存&#xff08;Framebuffer&#xff09;是图形渲染管线中的一个非常重要的概念&#xff0c;它用于存储渲染过程中产生的像素数据&#xff0c;并最终输出到显示器上。简单来说&#xff0c;帧缓存就是计算机图形中的“临时画布”&#xff0c;它储存渲染操作生成的图像数据&am…