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

news2025/1/10 21:38:44

文章目录

前言

一、单链表的概念与结构

1.概念

2.结点

3.性质

二、实现单链表

1.结构的定义

2.链表的打印和结点的申请

3.单链表的尾插和头插

4.单链表的尾删和头删

5.单链表的查找

6.指定位置之前插入数据和指定位置之后插入数据

7.删除pos结点和删除pos之后的结点

8.单链表的销毁

    总结


前言

各位好久不见,今天我将给大家更新线性表中的单链表,内容较多,希望大家能耐心地看完这篇博客。话不多说,接下来我详细介绍单链表。


一、单链表的概念与结构

1.概念

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

就比如,在生活中,我们坐火车去景点游玩,而火车就是一节一节的,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉加上,不会影响其他车厢,每节车厢都是独立存在的。

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

链表大致如图所示,链表是由一个一个结点前后相互连接而组成的。前一个节点通过存储后一个节点的地址来找到后一个节点,接下来细说结点的内容。

2.结点

定义:与顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,我们称之为“节点/结点”。
结点有两个组成部分:1.保存的数据    2.保存下一个结点的地址(指针变量)
在上图中指针 变量 plist 保存的是第一个结点的地址,我们称  plist  此时“指向”第一个结点,如果我们希望 plist “指向”第二个结点时,只需要修改 plist 保存的内容为 0x0012FFA0
链表中每个结点都是独立申请的(即需要插入数据时才去申请一块结点的空间),我们需要通过指针变量来保存下一个结点位置才能从当前结点找到下一个结点。 
对单链表中一个结点的结构体的定义:
typedef int SLTDataType;
//定义链表结点的结构
typedef struct SListNode
{
	SLTDataType data;//存储的数据
	struct SListNode* next;//下一个结点的地址
}SLTNode;//结点结构体的名字,起方便的作用

3.性质

1、链式机构在逻辑上是连续的,在物理结构上不一定连续
2、结点一般是从堆上申请的
3、从堆上申请来的空间,链表的节点是malloc来实现;是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续
4、当我们想要保存一个整型数据时,实际是向操作系统申请了一块内存,这个内存不仅要保存整型数据,也需要保存下一结点的地址(当下一个结点为空时保存的地址为空)。
5、当我们想要从第一个结点走到最后一个结点时,只需要在当前结点拿上下一个结点的地址就可以了。

二、实现单链表

1.结构的定义

在上面已介绍单链表中结点的结构定义,我们直接实现即可:

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

2.链表的打印和结点的申请

链表的打印:

写打印的函数时,函数名取为SLTPrint,创建一个指针变量pcur,让其从链表的头结点开始遍历,可避免指针在指向下个一个结点的时候把头结点给丢失。

void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur!=NULL)
	{
		printf("%d ->", pcur->data);
		pcur = pcur->next;//指向下一个结点
	}
	printf("NULL\n");//表示最后一个结点的下一个结点为NULL
}

结点的申请:

在顺序表中,空间满就需要进行扩容,而在链表中结点的申请也是这种类似的效果。因此,我们需要对链表进行一个结点一个结点的增加来进行扩容。

在写结点的申请的函数时,函数名取为SLTBuyNode (向操作系统买下来一个结点进行扩容), 在上面单链表的性质中,我们可知创建新结点需要用malloc来实现,如果新结点==NULL 就申请失败就退出,反之申请成功就让存储的数据 (node->data) 为x,下一个结点的地址(node->next)为NULL,最后直接返回node即可。

SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	if (node == NULL)
	{
		perror("malloc fail!\n");
		exit(1);
	}
	node->data = x;
	node->next = NULL;
	return node;
}

3.单链表的尾插和头插

尾插:

尾插是在最后一个结点的后面插入一个有效数据。先在插入数据之前先向空间申请一个结点大小调用SLTBuyNode()即可。再判断链表是否为空,为空就直接插入即头结点就是新结点;不为空就创建一个变量指针 pcur 从头开始遍历,走到尾结点就让尾结点的下一个结点指向新结点(pcur->next=newnode)。

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else {
		//当链表是非空,找尾结点
		SLTNode* pcur = *pphead;
		while (pcur->next!=NULL)
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;
	}

}

头插:

头插是在第一个结点前面插入一个数据,使数据指向第一个结点从而变成新的头结点。先操作系统申请一个结点调用SLTBuyNode()即可,先让新结点的下一个结点指向原来的头结点,这样原来的头结点直接变成新结点所以让新结点变成头结点即可。

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);//断言一下,避免链表为空的情况下进行操作
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

4.单链表的尾删和头删

尾删:

尾删是在一串结点里把最后一个结点进行删除(释放),并使倒数第二个结点指向NULL。先判断链表有一个结点和多个结点的情况:有一个结点时,直接对头结点进行释放再置为NULL;有多结点时,创建两个指针,一个 prev 用来保存尾结点的前一个结点,一个 ptail 从头开始遍历走到尾结点,对尾结点进行释放再置为NULL。

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead&&* pphead);
	//只有一个结点的情况
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else {
        //多结点的情况
		SLTNode* prev = NULL;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}
}

头删:

头删是在一串结点里把先把第二个结点保存下来再对第一个结点进行释放,并置NULL。先创建一个指针next,用next对头结点的下一个结点进行保存,再对头结点直接释放置为NULL,最后让头结点的下一个结点直接变成新的头结点。

void SLTPopFront(SLTNode** pphead)
{
	assert(pphead&&* pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next; 
}

5.单链表的查找

创建指针pcur,让pcur从头开始遍历,判断pcur指向的数据与查找的数据是否相同,如果相同就返回当前的节点;如果不相同让pcur指向下一个节点;循环结束完成都没找到则返回NULL。

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur != NULL)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//未找到
	return NULL;
}

6.指定位置之前插入数据和指定位置之后插入数据

指定位置之前插入数据:

创建两个指针,一个是prev从头开始遍历,一个是pos指定的位置;先判断头结点是不是指定位置,是就调用头删的函数,不是就让prev从头开始遍历直到prev->next为pos时就停止遍历;再让新节点的next指向pos,避免pos丢失;后让prev->next指向新节点。

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && pos);
	//当pos就是头结点时,相当于头插
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);//调用头插的函数
	}
	else {
		SLTNode* newnode = SLTBuyNode(x);
		SLTNode* prev = *pphead;
		while (prev->next!=pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}
}

指定位置之后插入数据:

在指定位置之后插入数据之前先创建一个指针pos指定位置,不需要从头开始遍历,直接让新节点的next指向pos的next;再让pos的next指向newnode。

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	//是在指定位置之后插入,因此是指向pos->next
	newnode->next = pos->next;
	pos->next = newnode;
}

7.删除pos结点和删除pos之后的结点

删除pos结点:

创建两个指针,一个是prev从头遍历,一个是pos指定位置先判断头结点是不是指定位置,是指定位置就调用头删的函数,不是指定位置就再让prev从头开始遍历;当prev->next等于pos时停止循环,开始对指定位置进行删除;再删除之前先对pos->next进行保留避免丢失,再对pos进行free后置为NULL。

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && pos);
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

删除pos之后的结点:

创建一个指针pos,直接对pos后面的节点进行销毁。先保存pos->next,再对pos进行free后置为NULL。

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

8.单链表的销毁

创建两个指针,一个是pucr从头开始遍历,一个是next用来保存下一个结点。next要走到pcur的前面,pcur用来销毁所在位置的,next是用来保存pcur->next的。

void SListDestroy(SLTNode** pphead)
{
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;//让头结点置为NULL这样才销毁彻底成功
}


总结

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

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

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

相关文章

DB-Engines Ranking 2025年1月数据库排行

DB-Engines Ranking 2025年1月数据库排行 DB-Engines排名根据数据库管理系统的受欢迎程度进行排名。排名每月更新一次。 2025年1月,共有423个数据库进入排行。 排行榜 Oracle Oracle 连续三月稳居榜首,排名稳定。2025 年 1 月分数较上月增 5.03&#x…

Hadoop3.x 万字解析,从入门到剖析源码

💖 欢迎来到我的博客! 非常高兴能在这里与您相遇。在这里,您不仅能获得有趣的技术分享,还能感受到轻松愉快的氛围。无论您是编程新手,还是资深开发者,都能在这里找到属于您的知识宝藏,学习和成长…

鸿蒙的APP真机调试以及发布

目录: 1、创建好鸿蒙项目2、创建AGC项目3、实现自动签名3.1、手动方式创建签名文件和密码 4、运行项目5、无线真机调试 1、创建好鸿蒙项目 2、创建AGC项目 (1)在File->Project Structure->Project->Signing Configs中进行登录。(未…

Swin Transformer模型详解(附pytorch实现)

写在前面 Swin Transformer(Shifted Window Transformer)是一种新颖的视觉Transformer模型,在2021年由微软亚洲研究院提出。这一模型提出了一种基于局部窗口的自注意力机制,显著改善了Vision Transformer(ViT&#xf…

穷举vs暴搜vs深搜vs回溯vs剪枝系列一>字母大小写全排列

题目&#xff1a; 解析&#xff1a; 代码&#xff1a; private List<String> ret;private StringBuffer path;public List<String> letterCasePermutation(String s) {ret new ArrayList<>();path new StringBuffer();dfs(s,0);return ret;}private voi…

LabVIEW软件侵权分析与应对

问&#xff1a;如果涉及到LabVIEW软件的仿制或模仿&#xff0c;特别是在功能、界面等方面&#xff0c;如何判断是否构成侵权&#xff1f;该如何应对&#xff1f; 答&#xff1a;LabVIEW软件的侵权问题&#xff0c;尤其是在涉及到仿制或模仿其功能、界面、设计等方面&#xff0…

玩转 JMeter:Random Order Controller让测试“乱”出花样

嘿&#xff0c;各位性能测试的小伙伴们&#xff01;今天咱要来唠唠 JMeter 里超级有趣又超实用的 Random Order Controller&#xff08;随机顺序控制器&#xff09;&#xff0c;它就像是性能测试这场大戏里的“魔术棒”&#xff0c;轻轻一挥&#xff0c;就能让测试场景变得千变…

探秘MetaGPT:革新软件开发的多智能体框架(22/30)

一、MetaGPT 引发的 AI 变革浪潮 近年来&#xff0c;人工智能大模型领域取得了令人瞩目的进展&#xff0c;GPT-3、GPT-4、PaLM 等模型展现出了惊人的自然语言处理能力&#xff0c;仿佛为 AI 世界打开了一扇通往无限可能的大门。它们能够生成流畅的文本、回答复杂的问题、进行创…

01、Redis初认识

一、简介 Redis&#xff0c;Remote Dictionary Server &#xff0c;远程字典服务。它是由一个意大利人使用C语言开发的&#xff0c;支持网络、可基于内存也可以持久化的日志型、NoSQL内存数据库&#xff0c;其提供了多种语言的API。 为什么把Reids称为字典服务&#xff1f; …

【2025 Rust学习 --- 10 运算符重载】

重载操作符 算术运算符与按位运算符 Rust 中&#xff0c;表达式 a b 实际上是 a.add(b) 的简写形式&#xff0c;也就是对标准库 中 std::ops::Add 特型的 add 方法的调用。Rust 的标准数值类型都实现了 std::ops::Add。 trait Add<Rhs Self> {type Output;fn add(se…

node-sass@4.14.1报错的最终解决方案分享

输入npm i全安装文件所需的依赖的时候&#xff0c;博主是使用sass去书写的&#xff0c;使用的是node-sass4.14.1和sass-loader7.3.1的版本的&#xff0c;安装的时候老是出现错误&#xff0c; node-sass4.14.1版本不再被支持的原因 node-sass 是一个基于 LibSass 的 Node.js 绑…

LabVIEW大数据有什么应用场景?

LabVIEW在处理大数据时主要依赖于其强大的数据采集、信号处理、控制、以及实时系统的功能。以下是一些典型的应用场景&#xff1a; ​ 1. 工业自动化与制造 数据采集与监控&#xff1a;在生产线上&#xff0c;LabVIEW可以用来收集大量的传感器数据&#xff08;如温度、压力、湿…

深入理解Mybatis原理》MyBatis的sqlSessi

sqlSessionFactory 与 SqlSession 正如其名&#xff0c;Sqlsession对应着一次数据库会话。由于数据库会话不是永久的&#xff0c;因此Sqlsession的生命周期也不应该是永久的&#xff0c;相反&#xff0c;在你每次访问数据库时都需要创建它&#xff08;当然并不是说在Sqlsession…

【OAuth2系列】如何使用OAuth 2.0实现安全授权?详解四种授权方式

作者&#xff1a;后端小肥肠 &#x1f347; 我写过的文章中的相关代码放到了gitee&#xff0c;地址&#xff1a;xfc-fdw-cloud: 公共解决方案 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; 【OAuth2系列】集成微…

Open WebUI 与 AnythingLLM 安装部署

在前文 Ollama私有化部署大语言模型LLM&#xff08;上&#xff09;-CSDN博客 中通过Ollama来搭建运行私有化大语言模型&#xff0c;但缺少用户交互的界面&#xff0c;特别是Web可视化界面。 对此&#xff0c;本文以Open WebUI和AnythingLLM为例分别作为Ollama的前端Web可视化界…

如何稳定使用 O1 / O1 Pro,让“降智”现象不再困扰?

近期&#xff0c;不少朋友在使用 O1 或 O1 Pro 模型时&#xff0c;都会碰到“降智”或“忽高忽低”的智力波动&#xff0c;比如无法识图、无法生成图片、甚至回答准确度也不稳定。面对这些问题&#xff0c;你是不是也感到头疼呢&#xff1f; 为了找到更可靠的解决办法&#xf…

RK3562编译Android13 ROOT固件教程,触觉智能开发板演示

本文介绍编译Android13 ROOT权限固件的方法&#xff0c;触觉智能RK3562开发板演示&#xff0c;搭载4核A53处理器&#xff0c;主频高达2.0GHz&#xff1b;内置独立1Tops算力NPU&#xff0c;可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。 关闭seli…

58. Three.js案例-创建一个带有红蓝配置的半球光源的场景

58. Three.js案例-创建一个带有红蓝配置的半球光源的场景 实现效果 本案例展示了如何使用Three.js创建一个带有红蓝配置的半球光源的场景&#xff0c;并在其中添加一个旋转的球体。通过设置不同的光照参数&#xff0c;可以观察到球体表面材质的变化。 知识点 WebGLRenderer …

React+redux项目搭建流程

1.创建项目 create-react-app my-project --template typescript // 创建项目并使用typescript2.去除掉没用的文件夹&#xff0c;只保留部分有用的文件 3.项目配置&#xff1a; 配置项目的icon 配置项目的标题 配置项目的别名等&#xff08;craco.config.ts&…

解决GitHub上的README.md文件的图片内容不能正常显示问题

一、问题描述 我们将项目推送到GitHub上后&#xff0c;原本在本地编写配置好可展现的相对路径图片内容&#xff0c;到了GitHub上却不能够正常显示图片内容&#xff0c;我们希望能够在GitHub上正常显示图片&#xff0c;如下图所示&#xff1a; 二、问题分析 现状&#xff1a;REA…