【数据结构】顺序表和链表——链表(包含大量经典链表算法题)

news2024/11/17 9:40:45

文章目录

  • 1. 单链表
    • 1.1 概念与结构
      • 1.1.1 结点
      • 1.1.2 链表的性质
      • 1.1.3 链表的打印
    • 1.2 实现单链表
    • 1.3 链表的分类
    • 1.4 单链表算法题
      • 1.4.1 移除链表元素
      • 1.4.2 反转链表
      • 1.4.3 链表的中间结点
      • 1.4.4 合并两个有序链表
      • 1.4.5 链表分割
      • 1.4.6 链表的回文结构
      • 1.4.7 相交链表
      • 1.4.8 环形链表1
      • 1.4.9 环形链表2
      • 1.4.10 随机链表的复制
  • 2. 双向链表
    • 2.1 概念与结构
    • 2.2 实现双向链表
  • 3. 顺序表与链表的对比分析

1. 单链表

1.1 概念与结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

在这里插入图片描述

淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉/加上,不会影响其他车厢,每节车厢都是独立存在的。

在链表里,每节“车厢”是什么样的呢?

在这里插入图片描述

1.1.1 结点

与顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,我们称之为“结点/节点

结点的组成主要有两个部分:当前结点要保存的数据和保存下一个结点的地址(指针变量)

图中指针变量plist保存的是第一个结点的地址,我们称plist此时“指向”第一个结点,如果我们希望plist“指向”第二个结点时,只需要修改plist保存的内容为0x0012FFA0即可。

链表中每个结点都是独立申请的(即需要插入数据时才去申请一块结点的空间),我们需要通过指针变量来保存下一个结点位置才能从当前结点找到下一个结点。

1.1.2 链表的性质

1、链式结构在逻辑上是连续的,在物理结构上不⼀定连续

2、结点一般是从上申请的

3、从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能连续,可能不连续

结合前面学到的结构体知识,我们可以给出每个结点对应的结构体代码:

假设当前保存的结点为整型:

struct SListNode
{
	int data; //结点数据
	struct SListNode* next; //指针变量⽤保存下⼀个结点的地址
};

当我们想要保存一个整型数据时,实际是向操作系统申请了一块内存,这个内存不仅要保存整型数据,也需要保存下⼀个结点的地址(当下⼀个结点为空时保存的地址为空)。

当我们想要从第一个结点走到最后一个结点时,只需要在当前结点拿上下一个结点的地址就可以了。

1.1.3 链表的打印

给定的链表结构中,如何实现结点从头到尾的打印?

在这里插入图片描述

思考:当我们想保存的数据类型为字符型、浮点型或者其他⾃定义的类型时,该如何修改?

1.2 实现单链表

SList.h

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data; //结点数据
	struct SListNode* next; //指针保存下一个结点的地址
}SLTNode;

void SLTPrint(SLTNode* phead);
//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode * SLTFind(SLTNode* phead, SLTDataType x);
//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** pphead);

上面这部分是链表实现所需要的节点结构和一些方法,其中具体的方法由读者自己先来尝试实现,如有不会的可以在讨论区询问,将会由作者或者其它积极的读者来解答❤️❤️❤️

1.3 链表的分类

链表的结构非常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:

在这里插入图片描述

链表说明:

在这里插入图片描述

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:单链表和双向链表

  1. 无头单向非循环链表(俗称:单链表):结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

  2. 带头双向循环链表(俗称:双向链表):结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

1.4 单链表算法题

1.4.1 移除链表元素

https://leetcode.cn/problems/remove-linked-list-elements/description/

OJ代码有bug怎么办?VS调试技能用起来

(1)将OJ代码复制粘贴到vs上

在这里插入图片描述

(2)创建测试方法,调用本次要调试的目标方法

在这里插入图片描述

(3)利用vs调试工具排查代码问题

在这里插入图片描述

1.4.2 反转链表

https://leetcode.cn/problems/reverse-linked-list/description/

1.4.3 链表的中间结点

https://leetcode.cn/problems/middle-of-the-linked-list/description/

1.4.4 合并两个有序链表

https://leetcode.cn/problems/merge-two-sorted-lists/description/

1.4.5 链表分割

https://www.nowcoder.com/practice/0e27e0b064de4eacac178676ef9c9d70

1.4.6 链表的回文结构

https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa

1.4.7 相交链表

https://leetcode.cn/problems/intersection-of-two-linked-lists/description/

在这里插入图片描述

1.4.8 环形链表1

https://leetcode.cn/problems/linked-list-cycle/description/

💡 快慢指针

快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的未尾

思考1:为什么快指针每次走两步,慢指针走一步可以相遇,有没有可能遇不上,请推理证明!

在这里插入图片描述

slow一次走一步,fast一次走2步,fast先进环,假设slow也走完入环前的距离,准备进环,此时fast和slow之间的距离为N,接下来的追逐过程中,每追击一次,他们之间的距离缩小1步

追击过程中fast和slow之间的距离变化:

在这里插入图片描述

因此,在带环链表中慢指针走一步,快指针走两步最终一定会相遇。

思考2:快指针一次走3步,走4步,…n步行吗?

step1:

按照上面的分析,假设慢指针每次走一步,快指针每次走三步,此时快慢指针的最大距离为N,接下来的追逐过程中,每追击一次,他们之间的距离缩小2步

追击过程中fast和slow之间的距离变化:

在这里插入图片描述

分析:

(1)如果N是偶数,第一轮就追上了

(2)如果N是奇数,第一轮追不上,快追上,错过了,距离变成-1,即C-1,进入新的一轮追击

​ a、C-1如果是偶数,那么下一轮就追上了

​ b、C-1如果是奇数, 那么就永远都追不上

总结一下追不上的前提条件: N是奇数,C是偶数

但是你以为就是这样吗?请看下面进一步分析

step2:

在这里插入图片描述

假设:

环的周长为C,头结点到slow结点的长度为L,slow走一步,fast走三步,当slow指针入环后,

slow和fast指针在环中开始进行追逐,假设此时fast指针已经绕环x周。

在追逐过程中,快慢指针相遇时所走的路径长度:

fast: L + x ∗ C + C − N L+x*C+C-N L+xC+CN

slow: L L L

由于慢指针走一步,快指针要走三步,因此得出: 3 ∗ 慢指针路程 = 快指针路程 3 * 慢指针路程 = 快指针路程 3慢指针路程=快指针路程,即:

3 L = L + x ∗ C + C − N 3L = L + x*C + C − N 3L=L+xC+CN

化简得: 2 L = ( x + 1 ) C − N 2L = (x + 1)C − N 2L=(x+1)CN

对上述公式继续分析:由于偶数乘以任何数都为偶数,因此 2 L 2L 2L 一定为偶数,则可推导出这个可能的情况:

  • 情况1:偶数 = 偶数 - 偶数

  • 情况2:偶数 = 奇数 - 奇数

由step1中(1)得出的结论,如果N是偶数,则第一圈快慢指针就相遇了。

由step1中(2)得出的结论,如果N是奇数,则fast指针和slow指针在第一轮的时候套圈了,开始进行下一轮的追逐;当N是奇数,要满足以上的公式,则 ( x + 1 ) C (x+1)C (x+1)C 必须也要为奇数

  • 其中如果C为偶数,则这里的 ( x + 1 ) C (x+1)C (x+1)C 为偶数,条件不满足,则step1中(2)b的C-1为奇数不存在

  • 如果C为奇数,满足这里的 ( x + 1 ) C (x+1)C (x+1)C 为奇数且符合step1中(2)a的C-1为偶数的结论,则快慢指针会相遇

因此, step1中的 N是奇数,C是偶数不成立 ,既然不存在该情况,则快指针一次走3步最终一定也可以相遇。

快指针一次走4、5…步最终也会相遇,其证明方式同上。

typedef struct ListNode ListNode;
bool hasCycle(struct ListNode* head) {
	ListNode * slow, * fast;
	slow = fast = head;
	while (fast && fast->next)
	{
		slow = slow->next;
		int n = 3; //fast每次⾛三步
		while (n--)
		{
			if (fast->next)
			fast = fast->next;
			else
				return false;
		}
		if (slow == fast)
		{
			return true;
		}
	}
	return false;
}

💡 提示

虽然已经证明了快指针不论走多少步都可以满足在带环链表中相遇,但是在编写代码的时候选择其它的步数方式会有额外的步骤引入,所以涉及到快慢指针的算法题中通常习惯使用慢指针走一步快指针走两步的方式。

1.4.9 环形链表2

https://leetcode.cn/problems/linked-list-cycle-ii/description/

💡 结论

让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时快慢指针相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇

证明以上结论

在这里插入图片描述

说明:

​ H为链表的起始点,E为环入口点,M与判环时候相遇点。

假设:

​ 环的长度为R,H到E的距离为L,E到M的距离为 X ,则:M到E的距离为 R-X。

在判环时,快慢指针相遇时所走的路径长度:

fast: L + X + n R L+X + nR L+X+nR

slow: L + X L+X L+X

注意:

1.当慢指针进入环时,快指针可能已经在环中绕了n圈了,n至少为1

​ 因为:快指针先进环走到M的位置,最后又在M的位置与慢指针相遇

2.慢指针进环之后,快指针肯定会在慢指针走一圈之内追上慢指针

​ 因为:慢指针进环后,快慢指针之间的距离最多就是环的长度,而两个指针在移动时,每次它们至少距离都缩减一步,因此在慢指针移动一圈之前快指针肯定是可以追上慢指针的,而快指针速度是慢指针的两倍,因此有如下关系是:

2 ∗ ( L + X ) = L + X + n R 2 * (L+X)=L+X+nR 2(L+X)=L+X+nR

—> L + X = n R L+X=nR L+X=nR

—> L = n R − X L=nR-X L=nRX

—> L = ( n − 1 ) R + ( R − X ) L = (n-1)R+(R-X) L=(n1)R+(RX)

(n为1,2,3,4…,n的大小取决于环的大小,环越小n越大)

极端情况下,假设n=1,此时: L=R-X

即:一个指针从链表起始位置运行,一个指针从相遇点位置绕环,每次都走一步,两个指针最终会在入口点的位置相遇。

1.4.10 随机链表的复制

https://leetcode.cn/problems/copy-list-with-random-pointer/description/

更多链表算法刷题入口:

牛客网:https://www.nowcoder.com/exam/oj

LeetCode:https://leetcode.cn/problems/copy-list-with-random-pointer/description/

2. 双向链表

2.1 概念与结构

在这里插入图片描述

💡注意

这里的“带头”跟前面我们说的“头结点”是两个概念,实际前面的在单链表阶段称呼不严谨,但是为了同学们更好的理解就直接称为单链表的头结点。

带头链表里的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这里“放哨的”

2.2 实现双向链表

List.h

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next; //指针保存下⼀个结点的地址
	struct ListNode* prev; //指针保存前⼀个结点的地址
	LTDataType data;
	}LTNode;

//void LTInit(LTNode** pphead);
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);

上面这部分是链表实现所需要的节点结构和一些方法,其中具体的方法由读者自己先来尝试实现,如有不会的可以在讨论区询问,将会由作者或者其它积极的读者来解答❤️❤️❤️

3. 顺序表与链表的对比分析

不同点顺序表链表(单链表)
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持:O(1)不支持:O(N)
任意位置插入或者删除元素可能需要搬移元素,效率低O(N)只需修改指针指向
插入动态顺序表,空间不够时需要扩容和空间浪费没有容量的概念,按需申请释放,不存在空间浪费
应用场景元素高效存储+频繁访问任意位置高效插入和删除

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

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

相关文章

【运维监控】influxdb 2.0+grafana 监控java 虚拟机以及方法耗时情况(完整版)

关于java应用的监控本系列有文章如下: 【运维监控】influxdb 2.0telegraf 监控tomcat 8.5运行情况 【运维监控】influxdb 2.0grafana 监控java 虚拟机以及方法耗时情况 【运维监控】Prometheusgrafana监控tomcat运行情况 【运维监控】Prometheusgrafana监控spring b…

软考科目傻傻分不清?一次搞懂各科目考核内容!小白不再纠结!

2024年下半年软考报名已经逐步进入尾声了,大部分考生都已完成报名,但有不少小白因为是第一次报考,对各科目傻傻分不清,导致报错了科目…… 这种情况很可能会影响考试,浪费了一次报考机会。而为了尽量避免这一情况再发生…

个人简历 (自己设计的)

欢迎大家来观看。 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" co…

国产“小钢炮”MiniCPM3-4B:小参数,大能量!

前沿科技速递&#x1f680; 在 AI 大模型浪潮中&#xff0c;国内厂商面壁智能再次突破&#xff0c;推出了其最新的“小钢炮”系列——MiniCPM 3.0。这款全新模型不仅实现了在移动设备上运行 GPT-3.5 级别的能力&#xff0c;而且具备超强的推理、检索与代码解释功能。MiniCPM 3.…

Python必知必会:程序员必须知道的22个Python单行代码!

今天给大家分享24个每个Python程序员都必须知道的单行代码&#xff0c;帮你写出更简洁、更优雅、更高效的代码。 1. 列表推导式 列表推导式&#xff08;List Comprehensions&#xff09;可以提供一种简洁的方式创建列表。相较于传统的循环&#xff0c;列表推导式更高效、可读…

240909-ChuanhuChatGPT集成Ollama的环境配置

A. 最终效果 B. 需求文件 requirements.txt (至少需要安装这个&#xff0c;具体参见官网)requirements_advanced.txt &#xff08;如果安装了Ollama&#xff0c;并且可以进行对话&#xff0c;可以不需要安装&#xff0c;具体参见官网&#xff09;requirements_succcess.txt&am…

Qt5.4.1连接odbc驱动操作达梦数据库

Qt5.4.1连接odbc驱动操作达梦数据库 1 环境介绍2 Qt5.4.1 安装2.1 图形化界面安装Qt5.4.12.2 配置Qt5.4.1 环境变量2.3 Qt5.4.1 生成 libqsqlodbc.so 并配置2.3.1 生成Makefile2.3.2 查看 libqsqlodbc.so 文件并配置 3 配置Qt测试用例4 达梦数据库学习使用列表 1 环境介绍 CPU…

SAP加密解密功能设计

SAP加密解密功能设计 【场景】与外围系统对接时&#xff0c;出于信息安全等因素&#xff0c;经常需要对传输的信息做加密解密控制。 1. 公用类zcl_aes_utility *----------------------------------------------------------------------* * CLASS ZCL_AES_UTILITY DEFI…

Ubuntu 22.04 安装增强功能失败

安装的时候&#xff0c;总是失败&#xff0c;然后根据提示查看 log 猜测可能需要安装g12 ubuntu22.04.2 目前(until 23.6.25) gcc 的默认版本是 11.3.0, 有些 c 的特性无法享用.Launchpad toolchain test buildsLanchpad toolchain build 将 Lanchpad 上的 PPA 加入到 apt 搜…

用Python包加速你的视频剪辑:Tailor工具全解析

Tailor是一款视频智能裁剪、视频生成和视频优化的视频剪辑工具。目前的目标是通过人工智能技术减少视频剪辑的繁琐操作&#xff0c;让普通人也能简单实现专业剪辑人的水准&#xff01;长远目标是让视频剪辑实现真正的AIGC&#xff01; 当然&#xff0c;这是一份Python包的列表…

分组注解和自定义注解及分页查询

自定义注解的使用步骤 案例&#xff1a; 此时state需要进行的校验使用普通方式无法满足&#xff0c;需要我们根据需求进行自定义注解 创建一个注解 Documented//元注解 Retention(RetentionPolicy.RUNTIME)//元注解 Constraint(validatedBy {StateValidation.class}//指定提供…

DPDK基础入门(七):网卡性能优化

DPDK的轮询模式 运行在操作系统内核态的网卡驱动程序基本都是基于异步中断处理模式&#xff0c;而DPDK采用了轮询或者轮询混杂中断的模式来进行收包和发包。 任何包进入到网卡&#xff0c;网卡硬件会进行必要的检查、计算、解析和过滤等&#xff0c;最终包会进入物理端口的某…

最高1000万 各地模型和算法备案补贴政策一览

最高1000万 各地模型和算法备案补贴政策一览 2024年7月31日&#xff0c;成都市的人工智能产业再度引起关注。通过国家大模型备案的三家企业——海艺互娱、晓多科技和明途科技&#xff0c;获得了成都市经信局市新经济委的百万奖励。这一奖励源自成都发布的《成都市进一步促进人工…

手把手带你拿捏指针(1)

文章目录 一、内存和地址1.内存编号、地址和指针的关系2.对于编址的理解 二、指针变量和地址1.取地址操作符&2.指针变量3.解引用操作符(*)4.指针变量的大小 三、指针变量类型的意义1.指针解引用2.指针-整数3.void*指针 四、const修饰指针1.const修饰变量2.const修饰指针变量…

避障小车—51单片机

一、小车底盘组装 根据视频的安装步骤安装 二、 电机模块开发 2.1 L9110s概述 接通VCC&#xff0c;GND 模块电源指示灯亮&#xff0c; 以下资料来源官方&#xff0c;但是不对&#xff0c;根据下节课实际调试 IA1输入高电平&#xff0c;IA1输入低电平&#xff0c;【OA1 OB1…

【项目二】C++高性能服务器开发——日志系统(终章)

感谢sylar&#xff0c;感谢开源笔记的所有人~ 知识点备忘录switch结合宏定义简化获取时间戳获取行号获取线程ID 知识点备忘录 上一篇适配器后得到的输出是下面这样&#xff0c;在main函数中定义了需要的一切&#xff0c;和项目所需要的还相差很远&#xff0c;比如日志级别需要…

通过AI来创建一个_____html css网页制作成品 例子演示

使用AI 输入创建一个 html css网页制作成品 例 然后出来 好的&#xff0c;我将为您创建一个简单的HTML和CSS网页制作的示例。这个示例将包括基本的布局、文本样式和一些内联的CSS样式。 { "name": "dalle", "description": "A simple exa…

价值流思维:全面提升业务效率与企业竞争力的核心方法论

价值流驱动的企业架构转型 在数字化浪潮的推动下&#xff0c;企业面临前所未有的竞争压力和业务复杂性。如何在动态市场环境中保持竞争优势&#xff0c;已经成为企业管理者亟待解决的问题。《价值流指南》由The Open Group发布的企业数字化转型专业参考指南&#xff0c;系统化…

为啥有人累死累活,还是穷?

咱们今天不聊怎么发财&#xff0c;来聊聊“为啥有人穷”。一说穷&#xff0c;大家第一反应就是钱不够花&#xff0c;但少有人想到&#xff0c;穷还可能是精神上的、思想上的、道德上的。表面看缺钱&#xff0c;背后往往有更深的原因。 记得《我不是药神》里那假药贩子张长林吗&…

【Redis】Redis 典型应用 - 缓存 (Cache) 原理与策略

目录 Redis 典型应⽤ - 缓存 (cache)什么是缓存使⽤ Redis 作为缓存缓存的更新策略1)定期⽣成2)实时生成 缓存预热&#xff0c;缓存穿透&#xff0c;缓存雪崩 和 缓存击穿关于缓存预热 (Cache preheating)什么是缓存预热 关于缓存穿透 (Cache penetration)什么是缓存穿透为何产…