【数据结构】单链表:头部操作我很行,插入也不用增容!!!

news2025/7/11 5:28:53

单链表

在这里插入图片描述
 
 
在这里插入图片描述

文章目录

  • 单链表
    • 1.链表
      • 1.1链表的概念和结构
      • 1.2链表的分类
    • 2.单链表的模拟实现
      • 2.1单链表的打印
      • 2.2单链表的尾插
      • 2.3单链表的头插
      • 2.4单链表的尾删
      • 2.5单链表的头删
      • 2.6单链表的查找
      • 2.7单链表的中间插入(在结点前插入)
      • 2.8单链表的中间删除(删除该结点)
      • 2.9单链表的中间插入(在结点后插入)
      • 2.10单链表的中间删除(删除该结点的后继)
      • 2.11单链表的销毁
    • 3.单链表的优缺点
    • 4. 链表的经典OJ题目训练

 

1.链表

 

1.1链表的概念和结构

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

在这里插入图片描述在这里插入图片描述
 ​
     现实中            数据结构中
 

 

在这里插入图片描述

 

1.2链表的分类

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

  1. 单向或者双向

    在这里插入图片描述

  2. 带头或者不带头(哨兵位的头结点)

    在这里插入图片描述

  3. 循环或者非循环

    在这里插入图片描述

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

在这里插入图片描述

  1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。这种结构在笔试面试中出现很多。
  2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势

 

2.单链表的模拟实现

SList.h

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

typedef int SLTDateType;

//单链表结点结构
typedef struct SLTNode
{
	SLTDateType data;
	struct SLTNode* next;
}SLTNode;

//单链表打印
void SLTPrint(SLTNode* phead);

//尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDateType x);

//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDateType x);
//结点前插入
void SLTInsert(SLTNode** pphead,SLTNode* pos, SLTDateType x);
//删除该结点
void SLTErase(SLTNode** pphead, SLTNode* pos);

//结点后插入
void SLTInsertAfter(SLTNode* pos, SLTDateType x);
//删除该结点的后继
void SLTEraseAfter(SLTNode* pos);
//单链表的销毁
void SLTDestroy(SLTNode** pphead);//free完所有结点,将phead置空

 

SList.c

💡ps:单链表是使用一个SLTNode*的指针plist(头指针)来维护的。

⚠注意1:

  • 由于要通过函数接口来实现单链表的增删查改(修改单链表),就要进行址传递,传入&plist

  • 对于不需要修改单链表的接口,直接传入plist即可

 

⚠注意2:

  • 由于单链表的初始化比较简单,我们在测试文件中(test.c)中,直接SLTNode* plist = NULL即可

  • 单链表的销毁并不是简单地对头指针进行free,而是要对每个结点进行free

 

2.1单链表的打印

void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}

	printf("NULL\n");
}

💡ps:由于一个结点存着下一个结点的地址,所以我们可以通过cur = cur->next的方式,来找下一个结点,遍历下去。

 

2.2单链表的尾插

SLTNode* BuyNewnode(SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		return NULL;
	}
	else
	{
		newnode->data = x;
		newnode->next = NULL;
	}

	return newnode;
}

//链表的插入操作是不需要对phead进行判空的(因为空链表,phead就是NULL)
//尾插
void SLTPushBack(SLTNode** pphead, SLTDateType x)
{
	assert(pphead);//防止使用者传参时,传的plist而不是&plist
	特殊情况,如果是空链表,找不到尾结点,所以直接将newnode作为phead

	//1.构造新结点
	SLTNode* newnode = BuyNewnode(x);

	//特殊情况:空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	else//一般情况:非空链表
	{
		//2.找到尾结点
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}

		//3.嫁接
		tail->next = newnode;
	}

}

💡ps:对于单链表的尾部操作,需要先找到尾结点。时间复杂度:O(N)。

而且对于空链表的情况无法找到尾结点,需要特殊处理。

 

2.3单链表的头插

//头插
void SLTPushFront(SLTNode** pphead, SLTDateType x)
{
	assert(pphead);
	SLTNode* newnode = BuyNewnode(x);
	newnode->next = *pphead;
	*pphead = newnode;
	SLTInsert(pphead, *pphead, x);
}

💡ps:头部操作只需要注意连接关系即可,时间复杂度为:O(1)

 

2.4单链表的尾删

//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);//删除操作,链表不可为空
	//特殊情况,如果链表只有一个结点是找不到前驱的,要进行特殊处理

	SLTNode* tail = *pphead;
	if (tail->next == NULL)//1个结点的情况
	{
		free(tail);
		*pphead = NULL;
	}
	else//多个结点的情况
	{
		SLTNode* prev = NULL;

		//1.找到尾结点和尾结点的前驱
		while (tail->next != NULL)
		{
			prev = tail;//每次去下一个,用prev记录下来
			tail = tail->next;
		}
		//2.删除尾结点,进行嫁接和NULL
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
	
}

💡ps:找尾,时间复杂度为O(N)

由于尾删需要找尾结点的前驱,而只有一个结点的链表无法找到尾结点前驱,需要进行特殊处理。

 

2.5单链表的头删

void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);

	SLTNode* first = *pphead;
	*pphead = first->next;
	free(first);
}

💡ps:时间复杂度为O(N)

 

2.6单链表的查找

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDateType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}

	return NULL;
}

 

2.7单链表的中间插入(在结点前插入)

//结点前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	assert(pphead);
	assert(pos);

	SLTNode* newnode = BuyNewnode(x);
	//特殊情况
	//头插
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	SLTNode* prev = *pphead;
	//1.找pos的前驱
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	//2.嫁接
	prev->next = newnode;
	newnode->next = pos;
}

💡ps:由于需要找结点pos的前驱,时间复杂度O(N)

pos为首结点时,无法找到pos的前驱需要进行特殊处理(头插操作)

 

2.8单链表的中间删除(删除该结点)

//删除该结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	//特殊情况
	//头删
	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}

	SLTNode* prev = *pphead;
	//1.找前驱
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	//2.嫁接
	prev->next = pos->next;
	free(pos);
}

💡ps:由于需要找结点pos的前驱,时间复杂度O(N)

pos为首结点时,无法找到pos的前驱需要进行特殊处理(头删操作)

 

2.9单链表的中间插入(在结点后插入)

//结点后插入
void SLTInsertAfter(SLTNode* pos, SLTDateType x)
{
	assert(pos);
	SLTNode* newnode = BuyNewnode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

 

2.10单链表的中间删除(删除该结点的后继)

//删除该结点的后继
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);//不能尾删
	SLTNode* next = pos->next;
	pos->next = next->next;

	free(next);
	next = NULL;
}

 

2.11单链表的销毁

void SLTDestroy(SLTNode** pphead)//free完所有结点,将phead置空
{
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}

	*pphead = NULL;
}

 

3.单链表的优缺点

优点:
1. 头部操作,效率很高,时间复杂度为O(1)
2. 结点之间不是连续存储的,在进行插入操作时,无法考虑增容。

 

缺点:
1. 不支持随机访问,即使知道是第几个结点,也要进行遍历才能找到该结点。
2. 尾部操作要找结点,效率低下(如果使用一个直接来指向尾结点,也可实现O(1)的效率)
 
💡ps:对于空链表进行尾部操作时,需要进行特殊处理,这个时候我们可以构造一个哨兵位的头结点,使问题简化。

 

4. 链表的经典OJ题目训练

  1. 删除链表中等于给定值 val 的所有结点。 OJ链接

  2. 反转一个单链表。 OJ链接

  3. 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。OJ链接

  4. 输入一个链表,输出该链表中倒数第k个结点。 OJ链接

  5. 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。OJ链接

  6. 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。OJ链接

  7. 链表的回文结构。OJ链接

  8. 输入两个链表,找出它们的第一个公共结点。OJ链接

  9. 给定一个链表,判断链表中是否有环。 OJ链接

  10. 给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 NULL OJ链接

  11. 给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点或空结点。

    要求返回这个链表的深度拷贝。OJ链接

  12. 其他 。ps:链表的题当前因为难度及知识面等等原因还不适合我们当前学习,以后大家自己下去以后 Leetcode OJ链接 + 牛客 OJ链接

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

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

相关文章

如何学好市场营销?

首先&#xff0c;要学好市场营销学肯定是需要看书的&#xff0c;可以多看些类似《经理人参阅&#xff1a;市场营销》一类的经典书籍&#xff0c;虽然比普通书贵些但是值。注意该书只有其官网有&#xff0c;请自行百度。看书在于精而不在于多&#xff0c;个人认为要学好市场营销…

软考高级备考哪一个类型好些?

软考高级是比中级和初级难&#xff0c;科目就要考三科&#xff0c;选择题基础知识简答题案例分析写作论文 软考高级科目有&#xff1a;信息系统项目管理师、系统分析师、系统架构设计师、网络规划师、系统规划与管理师。如下&#xff1a; 软考高级中高项信息系统项目管理师师比…

Qt 贴图实现方向控制盘

一、效果走一波 二、使用贴图进行不规则按钮的设计与开发 开发环境描述&#xff1a;QtCreator Qt Desinger &#xff08;1&#xff09;首先准备待贴的图片 ​ 图片的切片大小必须一样&#xff0c;背景为透明的&#xff1b;将待贴的所有图片都切下来&#xff0c;文件标明名称…

移动App性能测试包含哪些内容?App性能测试工具有哪些?

随着互联网高科技的蓬勃发展&#xff0c;移动app的的需求量和供给量都较大。但一款好app的成功上线以及为用户带来高效体验&#xff0c;性能测试起着关键性的作用。性能测试是通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试&#xff0…

【STM32存储器映射-寄存器基地址-偏移】

前言 在学习STM32的时候&#xff0c;我们看到很多的寄存器编程&#xff0c; 比方说LED灯&#xff1a; //GPIOB.5端口输出高电平GPIOB->ODR|1<<5; //PB.5 输出高GPIOE->ODR|1<<5; //PE.5输出高 //GPIOB端口全部输出高电平*(unsigned int*)(0x4001 …

从深分页查询到覆盖索引

最近看到一道面试题&#xff0c;如何优化深分页查询 最简单的例子是 select * from web_bill_main limit 30000,10;分页达到30000行&#xff0c;需要把前面29999行都过滤掉&#xff0c;才能找到这10条数据 所以整体时间花了80ms(工具显示时间) 我当时的第一反应是&#xff0…

python Django的admin后台建设

什么是admin管理后台 1、django提供了完善的后台管理数据库的接口&#xff0c;可供开发过程中调用和测试使用2、django 会搜集所有已注册的模型类&#xff0c;为这些模型类提供数据管理界面&#xff0c;供开发者使用admin配置步骤1、创建后台管理账号-该账号为管理后台最高权限…

193、【动态规划】AcWing —— 291. 蒙德里安的梦想:状压dp详细解析(C++版本)

题目描述 原题链接&#xff1a;291. 蒙德里安的梦想 解题思路 &#xff08;1&#xff09;状态压缩dp先导知识 状态压缩会用二进制位来存储状态信息&#xff0c;在状态计算时&#xff0c;将整数转化为二进制爹形式进行计算。 可表示的状态就是 2n2^n2n 个。 &#xff08;2&…

python 列表删除多个元素

文章目录一. 删除列表多个元素方法1 使用枚举&#xff1a;2. 使用python中List.pop()方法3. 使用python中List.remove()方法4. 注意二. 使用双指针法删除列表多个元素1. 问题描述&#xff1a;2. 解决方法&#xff1a;3. 代码如下&#xff1a;三. 总结四. 相关链接一. 删除列表多…

扩盘操作LVM扩容操作-Centos7

生产环境要扩容&#xff0c;太久没试过LVM&#xff0c;记录一下走过的坑 [rootarchive ~]# df -h #查看磁盘挂载&#xff0c;对/dev/mapper/vgnfs-lvdata进行扩容 文件系统 容量 已用 可用 已用% 挂载点 devtmpfs 909M 0 909M 0…

文件上传漏洞知识总结

直接使用别人的靶场总感觉不太好&#xff0c;那么就干脆自己写一个自己的文件上传靶场吧。正好博客之前也没有单独总结过文件上传的知识点&#xff0c;那么就顺便水一篇文章&#xff0c;岂不是一举两得。当然关于文件上传 upload-labs 总结的比较全面了&#xff0c;非强迫症患者…

HTTP协议详解(上)

目录 前言&#xff1a; 认识URL HTTP协议方法 通过Fiddler抓包 GET和POST之间典型区别 header详解 HTTP响应状态码 常见状态码解释 状态码分类 HTTP协议报文格式 小结&#xff1a; 前言&#xff1a; HTTP协议属于应用层协议&#xff0c;称为超文本传输协议&#xff…

aws dynamodb 基础概念和理论

参考资料 https://amazon-dynamodb-labs.workshop.aws/https://docs.amazonaws.cn/amazondynamodb/latest/developerguide/Introduction.html dynamodb的工作原理 核心概念 table、item和attributes是dynamodb的核心组件&#xff0c;可以分别对应关系型数据库中的表&#x…

JavaScript新手学习手册-基础代码(三)

与上篇博客相接 一&#xff1a;Date对象 var date new Date();console.log(date); //全部时间console.log(date.getFullYear()); //年console.log(date.getMonth()); //月console.log(date.getDay()); //星期几console.log(date.getHours()) //时console.log(d…

java实现Hbase 增删改查

目录 一、新建一个maven工程 二、代码实现 2.1、配置hbase信息&#xff0c;连接hbase数据库 2.2、创建命名空间 2.3、创建表 2.4、删除表&#xff0c;删除之前要设置为禁用状态 2.5、添加数据 2.6、获取命令表空间 / tables列表 2.7、get方法查看表的内容 2.8、scan方法…

腾势D9改装来了,帮大家总结了一些需要改装的项目

最近腾势D9真的太火了&#xff0c;不仅外观霸气&#xff0c;内饰也是非常豪华。 1月份销量在MPV里已经排名第二了&#xff0c;性价比很高。 这边整理了一些改装项目供大家参考&#xff0c;有什么想法可以评论区一起讨论哦1. 电吸门 有车主吐槽车门难关&#xff0c;由于车内空间…

计算机网络:ICMP协议

网际控制报文协议ICMP ICMP协议支持主机或者路由器差错报告和网络探询 类型表明ICMP报文是哪类检验和&#xff1a;检验整个ICMP报文ICMP报文可分为ICMP差错报文和ICMP询问报文。 ICMP差错报告报文 终点不可达&#xff1a;当路由器或者主机不能交付数据报时&#xff0c;向源站…

Spring - Spring框架概述面试题总结

文章目录01. 什么是Spring&#xff1f;02. Spring框架的设计目标&#xff0c;设计理念&#xff0c;和核心是什么&#xff1f;03. Spring的优点是什么&#xff1f;04. Spring框架中都用到了哪些设计模式&#xff1f;05. Spring有哪些应用场景?06. Spring由哪些模块组成&#xf…

基于DDD的微服务落地

DDD四层架构对实时性要求高的强一致性业务场景&#xff0c;可采取分布式事务。分布式事务有性能代价&#xff0c;在设计时需要平衡考虑业务拆分、数据一致性、性能和实现的复杂度&#xff0c;尽量避免分布式事务的产生。领域事件驱动的异步方式是分布式架构常用的设计方式&…

【python】使用python将360个文件夹里的照片,全部复制到指定的文件夹中,并且按照顺序重新命名

最近要做一个图像生成的课题&#xff0c;在网上找了一个混合的数据集。这个数据集中一共有360个文件夹&#xff0c;然后文件夹中有6-9张不等的照片&#xff0c;我的目标就是编写python代码将所有的照片取出来&#xff0c;放到一个指定的文件夹里&#xff0c;并且从1开始按照顺序…