【数据结构初阶】一篇文章带你超深度理解【单链表】

news2025/1/17 3:17:49

 hi !

目录

前言:

1、链表的概念和结构

2、单链表(Single List,简写SList)的实现

2.1   定义链表(结点)的结构

2.2  创建一个链表

2.3  打印链表

2.4  尾插

2.5  头插

2.6  尾删

2.7  头删

2.8  查找

2.9  在指定位置之前插入数据

2.10  在指定位置之后插入数据

2.11  删除pos结点

2.12  删除pos之后的结点

2.13  销毁链表

————————————————  《 你的名字 》  ————————————————


正文开始——

前言:

前面我们学习了顺序表,实现了对数组内容增删查改等操作,但是顺序表仍然存在一些缺陷。

  1. 中间/头部的插入删除,时间复杂度为O(N);
  2. 增容需要申请新空间,拷贝数据,释放旧空间,这是不小的消耗;
  3. 增容一般是成2倍的增长,大概率会有一些空间的浪费。

那我们该如何解决上面的问题呢?下面我们来学习一下链表 。

1、链表的概念和结构

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

2、单链表(Single List,简写SList)的实现

上面我们了解了链表的概念和结构,链表又分为很多种,今天我们先学习链表之一单链表。

2.1   定义链表(结点)的结构

//定义链表(结点)的结构

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

2.2  创建一个链表

这里申请空间使用 malloc,在链表里面不存在增容的操作,想插入数据直接申请一个新的结点即可!用 calloc 也可以,calloc会让申请空间的内容初始化为0。

【代码】

test.c

//创建一个链表
void creatSList()
{
	//链表是由一个一个的结点组成的

	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	node1->data = 1;

	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	node2->data = 2;

	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	node3->data = 3;

	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
	node4->data = 4;

	//将结点之间连接起来
	node1->next=node2;
	node2->next=node3;
	node3->next=node4;
	node4->next = NULL;

}

2.3  打印链表

【思路图解】

【代码】 

//链表的打印

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

2.4  尾插

【思路图解】

【代码】 

SList.c

//申请一个新结点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	if (node == NULL)
	{
		perror("malloc file!");
		exit(1);
	}
	node->data = x;
	node->next = NULL;

	return node;
}

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//申请一个新结点
	SLTNode* newnode = SLTBuyNode(x);

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* pcur = *pphead;

		//找尾结点
		while (pcur->next)
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;
	}
}

test.c

void SListTest01()
{
	SLTNode* plist = NULL;
    //plist为指向第一个结点的指针
	SLTPushBack(&plist,100);
	SLTPrint(plist);
}

int main()
{
	SListTest01();
	return 0;
}

【验证】 

【注意】

  1. 在 test.c 里面尾插时传的是&plist ,而不是 plist。我们希望形参的改变影响实参,所以我们取实参的地址。plist 作为参数传的是一个指针变量而不是一个地址,只有 &plist (有&)才算是真正的取地址使得形参的改变影响实参。用二级指针来接收。
  2. 找尾结点。循环的条件是 pcur->next,而不是 pcur。因为 pcur->next 为NULL跳出循环时,说明 pcur 为尾结点,当 pcur 为空跳出循环时,说明 pcur 是尾结点的下一个节点,而不是尾结点。
  3. pphead==&plist;*pphead==plist(指向第一个结点的指针) 。
  4. assert (pphead);传过来的地址不能为空。

2.5  头插

各结点的地址应为0x0012FF...,在此纠正下面的错误。

【思路图解】

【代码】

SList.c

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	//申请一个新结点
	SLTNode* newnode = SLTBuyNode(x);

	newnode->next = *pphead;
	*pphead = newnode;
}

test.c


void SListTest01()
{
    //验证尾插
	SLTNode* plist = NULL;
	SLTPushBack(&plist,1);
	SLTPushBack(&plist,2);
	SLTPushBack(&plist,3);
	SLTPushBack(&plist,4);
	SLTPushBack(&plist,100);
	SLTPrint(plist);
    
    //验证头插
	SLTPushFront(&plist, 200);
	SLTPrint(plist);
}

int main()
{
	SListTest01();
	return 0;
}

【验证】

2.6  尾删

各结点的地址应为0x0012FF...,在此纠正下面的错误。谅解哈~~~

【思路图解】

当链表内不止一个结点时

当链表内只有一个结点时 

【代码】

SList.c


//尾删
void SLTPopBack(SLTNode** pphead)
{
	//链表为空,不可删除
	assert(pphead && *pphead);

	//处理只有一个结点的情况,要删除的就是头结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//找 ptail 和 prev
		SLTNode* ptail = *pphead;
		SLTNode* prev = NULL;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}
}

test.c


void SListTest01()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist,1);
	SLTPushBack(&plist,2);
	SLTPushBack(&plist,3);
	SLTPushBack(&plist,4);
	SLTPushBack(&plist,100);
	SLTPrint(plist);

	/*SLTPushFront(&plist, 200);
	SLTPrint(plist);*/

	SLTPopBack(&plist);
	SLTPrint(plist);
}

int main()
{
	/*createSList();*/
	SListTest01();
	return 0;
}

【验证】

2.7  头删

各结点的地址应为0x0012FF...,在此纠正下面的错误。

【思路图解】

【代码】

SList.c

//头删
void SLTPopFront(SLTNode** pphead)
{
	//链表不为空
	assert(pphead && *pphead);

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

test.c

void SListTest01()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist,1);
	SLTPushBack(&plist,2);
	SLTPushBack(&plist,3);
	SLTPushBack(&plist,4);
	SLTPushBack(&plist,100);
	SLTPrint(plist);

	/*SLTPushFront(&plist, 200);
	SLTPrint(plist);*/

	/*SLTPopBack(&plist);*/
	/*SLTPrint(plist);*/

	SLTPopFront(&plist);
	SLTPrint(plist);

}

int main()
{
	/*createSList();*/
	SListTest01();
	return 0;
}

【验证】

2.8  查找

各结点的地址应为0x0012FF...,在此纠正下面的错误。

思路简单,直接上代码:

SList.c

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

【注意】SLTNode* pcur = phead,重新定义一个指针变量 pcur 指向第一个结点,让 pcur 来遍历链表,防止头结点丢失。

test.c

void SListTest01()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist,1);
	SLTPushBack(&plist,2);
	SLTPushBack(&plist,3);
	SLTPushBack(&plist,4);
	SLTPushBack(&plist,100);
	SLTPrint(plist);

	/*SLTPushFront(&plist, 200);
	SLTPrint(plist);*/

	/*SLTPopBack(&plist);*/
	/*SLTPrint(plist);*/

	SLTPopFront(&plist);
	SLTPrint(plist);

	SLTNode* find = SLTFind(plist, 2);
	if (find == NULL)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了\n");
	}
}

【验证】

2.9  在指定位置之前插入数据

各结点的地址应为0x0012FF...,在此纠正下面的错误。

【思路图解】

【代码】

SList.c


//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);

	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		//申请一个新的结点
		SLTNode* newnode = SLTBuyNode(x);

		//找pos的前一个结点prev
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}
}

test.c

void SListTest01()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist,1);
	SLTPushBack(&plist,2);
	SLTPushBack(&plist,3);
	SLTPushBack(&plist,4);
	SLTPrint(plist);

	/*SLTPushFront(&plist, 200);
	SLTPrint(plist);*/

	/*SLTPopBack(&plist);*/
	/*SLTPrint(plist);*/

	//SLTPopFront(&plist);
	//SLTPrint(plist);

	//SLTNode* find = SLTFind(plist, 2);
	/*///*if (find == NULL)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了\n");
	//}*/

	SLTNode* find = SLTFind(plist, 2);
	SLTInsert(&plist, find, 100);
	SLTPrint(plist);

}

【验证】

2.10  在指定位置之后插入数据

各结点的地址应为0x0012FF...,在此纠正下面的错误。

【思路图解】

【代码】

SList.c

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

test.c

void SListTest01()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist,1);
	SLTPushBack(&plist,2);
	SLTPushBack(&plist,3);
	SLTPushBack(&plist,4);
	SLTPrint(plist);

	/*SLTPushFront(&plist, 200);
	SLTPrint(plist);*/

	/*SLTPopBack(&plist);*/
	/*SLTPrint(plist);*/

	//SLTPopFront(&plist);
	//SLTPrint(plist);

	//SLTNode* find = SLTFind(plist, 2);
	/*///*if (find == NULL)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了\n");
	//}*/

	/*SLTNode* find = SLTFind(plist, 2);
	SLTInsert(&plist, find, 100);
	SLTPrint(plist);*/

	SLTNode* find = SLTFind(plist, 2);
	SLTInsertAfter(find, 100);
	SLTPrint(plist); 

}

【验证】

2.11  删除pos结点

各结点的地址应为0x0012FF...,在此纠正下面的错误。

【思路图解】

【代码】

SList.c

//删除pos结点
void SLTErase(SLTNode** pphead,SLTNode* pos)
{
	assert(pphead && *pphead);
    //这里对pos进行限制,链表中必须有pos这个结点,在查找中若没有pos结点则返回NULL
	assert(pos);

	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		//找到prev:pos的前结点
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

test.c

void SListTest01()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist,1);
	SLTPushBack(&plist,2);
	SLTPushBack(&plist,3);
	SLTPushBack(&plist,4);
	SLTPrint(plist);

	/*SLTPushFront(&plist, 200);
	SLTPrint(plist);*/

	/*SLTPopBack(&plist);*/
	/*SLTPrint(plist);*/

	//SLTPopFront(&plist);
	//SLTPrint(plist);

	//SLTNode* find = SLTFind(plist, 2);
	/*///*if (find == NULL)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了\n");
	//}*/

	/*SLTNode* find = SLTFind(plist, 2);
	SLTInsert(&plist, find, 100);
	SLTPrint(plist);*/

	/*SLTNode* find = SLTFind(plist, 2);
	SLTInsertAfter(find, 100);
	SLTPrint(plist); */

	SLTNode* find = SLTFind(plist, 2);
	SLTErase(&plist, find);//这里的find由pos接收,SLTFind若没找到存储2这个结点则返回NULL
	SLTPrint(plist); 

}

【验证】

2.12  删除pos之后的结点

各结点的地址应为0x0012FF...,在此纠正下面的错误。

【思路图解】

【代码】

SList.c

//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

test.c

void SListTest01()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist,1);
	SLTPushBack(&plist,2);
	SLTPushBack(&plist,3);
	SLTPushBack(&plist,4);
	SLTPrint(plist);

	/*SLTPushFront(&plist, 200);
	SLTPrint(plist);*/

	/*SLTPopBack(&plist);*/
	/*SLTPrint(plist);*/

	//SLTPopFront(&plist);
	//SLTPrint(plist);

	//SLTNode* find = SLTFind(plist, 2);
	/*///*if (find == NULL)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了\n");
	//}*/

	/*SLTNode* find = SLTFind(plist, 2);
	SLTInsert(&plist, find, 100);
	SLTPrint(plist);*/

	/*SLTNode* find = SLTFind(plist, 2);
	SLTInsertAfter(find, 100);
	SLTPrint(plist); */

	//SLTNode* find = SLTFind(plist, 2);
	//SLTErase(&plist, find);
	//SLTPrint(plist); 

	SLTNode* find = SLTFind(plist, 2);
	SLTEraseAfter(find);
	SLTPrint(plist); 

}

【验证】

2.13  销毁链表

各结点的地址应为0x0012FF...,在此纠正下面的错误。

【思路图解】

【代码】

SList.c

//销毁链表
void SListDestroy(SLTNode** pphead)
{
	assert(pphead && *pphead);

	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
    //*pphead=plist,链表为空,也要把*pphead置为空
	*pphead = NULL;
}

【验证】

今天单链表的深度学习就结束啦,拜拜~~~


完——

————————————————  《 你的名字   ————————————————

 重要的人,不能忘记的人,不想忘记的人,你,是谁?

 不管在哪里,不管过多久,不管是要跨过高山,还是跨过湖海,我一定会去见你一面。

スパークル_RADWIMPS_高音质在线试听_スパークル歌词|歌曲下载_酷狗音乐酷狗音乐为您提供由RADWIMPS演唱的高清音质无损スパークルmp3在线听,听スパークル,只来酷狗音乐!icon-default.png?t=N7T8https://t4.kugou.com/song.html?id=avhoFadCPV2

至此结束——

再见——

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

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

相关文章

Aigtek高压放大器指标参数要求及其应用

高压放大器是一类特殊的放大器,其主要功能是将输入的低电压信号放大为输出的高电压信号。在各种应用中,如音频放大、通信系统、医学设备等,高压放大器都扮演着至关重要的角色。为了确保高压放大器能够满足实际应用的需求,并且具有…

Redis持久化和集群模式

目录 1、什么是持久化? 2、Redis实现持久化的方式 3、RDB(Redis DataBase)快照模式 3.1 手动触发 3.1.1 save 3.1.2 bgsave 3.2 自动触发 4、AOF(append only File)日志追加模式 4.1 开启aof 4.2 RBD和AOF的区…

mysql JSON特性优化

有朋友问到,mysql如果要根据json中的某个属性过滤,数据量大的话,性能很差,要如何提高性能? 为什么要用json串? 由于一些特定场景,mysql需要用到json串,例如文档,不同的…

【Matlab 传感器布局优化】基于群智能算法的wsn覆盖优化研究

一 背景介绍 无线传感器网络(Wireless Sensor Network, WSN)作为远程环境监测系统应用的关键技术,能够在有限的能源供应下提供高效的传感和通信服务。覆盖控制是保证高效通信和可靠数据传输的重要手段。鉴于复杂的物理环境限制了节点部署方式…

《JavaEE篇》--多线程(2)

《JavaEE篇》--多线程(1) 线程安全 线程不安全 我们先来观察一个线程不安全的案例: public class Demo {private static int count 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() -> {//让count自增5W次…

zabbix“专家坐诊”第247期问答

问题一 Q:乐维MCM免费版还需要再单独安装一个Zabbix连接么? A:估计是perseusZ_server服务进程因为重复安装导致服务挂了。试下能不能启动起来?service perseusZ_server status Q:正常启动的,页面刷新了还…

谷粒商城实战笔记-50-51-商品分类的删除

文章目录 一,50-商品服务-API-三级分类-删除-逻辑删除1,逻辑删除的配置1.1 配置全局的逻辑删除规则(可省略)1.2 配置逻辑删除Bean(可省略)1.3 Bean相应字段上加上注解TableLogic 2,后台接口开发…

力扣高频SQL 50题(基础版)第十题

文章目录 力扣高频SQL 50题(基础版)第十题1661. 每台机器的进程平均运行时间题目说明思路分析实现过程准备数据实现方式结果截图总结 力扣高频SQL 50题(基础版)第十题 1661. 每台机器的进程平均运行时间 题目说明 表: Activity…

数里行间创始人郭振:AIGC如何打造跨境电商增长新引擎

导读:在跨境电商领域,AI客服已经能够实现50%的问题平均解决率和不低于人工标准的客户满意度。 在生成式AI的商业化应用领域,跨境电商行业一直走在前列,成为最早实现技术落地的先锋之一。 “我们的AI客服机器人,已经帮助…

01 MySQL

文章目录 MySQL1、数据库相关概念2、MySQL3、SQL概述4、DDL:数据库操作5、DDL:表操作6、DML7、DQL8、约束9、数据库设计10、多表查询11、事务 MySQL 1、数据库相关概念 数据库 (1)存储和管理数据的仓库,数据是有组织的进行存储。 &#xff0…

【接口测试】params传参与body传参区别

文章目录 一.params传参二.body传参三.两者区别说明 一.params传参 params传参一般用于get请求 params传参时,参数会附于URL后面以问号形式展示。 示例: http://ip地址:端口号/login?usernamexm&pwd111二.body传参 body传参一般用于post请求 body传参时需…

2022真题-架构师案例(二)

1、某大型电商平台建立了一个在线B2B商店系统,并在全匡多地建设了货物仓储中心,通过提前备货的方式来提高货物的运送效率。但是在运营过程中,发现会出现很多跨仓储中心调货从而延误货物运送的情况。为此,该企业计划新建立一个全国…

搜维尔科技:Cyber​​glove通过其前所未有的柔性传感器技术,带来了多年的经验、专业知识和可靠性

Cyberglove 概述 新一代数据手套技术 MoCap 手套采用了原始 CyberGlove 产品 20 年经验所建立的技术,产生了改进的和新的特性、能力和设计,非常适合动作捕捉环境。 旧与新相遇, Cyberglove 通过其前所未有的柔性传感器技术,带来…

【深度学习】PyTorch框架(5):Transformer和多注意力机制

1、引言 在本文中,我们将探讨近两年来最具影响力的模型架构之一——Transformer模型。自从2017年Vaswani等人发表的论文《注意力是你所需要的全部》以来,Transformer架构在多个领域持续刷新着性能记录,尤其是在自然语言处理(NLP&…

【网络安全的神秘世界】 文件上传及验证绕过

🌝博客主页:泥菩萨 💖专栏:Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 这个漏洞对于初学者好挖,先找到文件上传的位置 文件上传是web网页中常见的功能之一,通常情况下恶意文…

Windows 11 系统对磁盘进行分区保姆级教程

Windows 11磁盘分区 磁盘分区是将硬盘驱动器划分为多个逻辑部分的过程,每个逻辑部分都可以独立使用和管理。在Windows 11操作系统中进行磁盘分区主要有以下几个作用和意义: 组织和管理数据:分区可以帮助用户更好地组织他们的数据&#xff0c…

怎么使用动态IP地址上网

如何设置动态IP地址上网? 设置动态IP地址上网的步骤如下: 一、了解动态IP地址 动态IP地址是由网络服务提供商(ISP)动态分配给用户的IP地址,它会根据用户的需求和网络情况实时改变。相比于静态IP地址,动态…

闲鱼、抖音、快手纷纷入局,“谷子”经济千亿市场纷争再起

二次元的狂热,终于还是蔓延到了三次元。 此前,在咸鱼上的一场拍卖中,一块直径75mm,重达20克的“吧唧”(徽章),以7.2万人民币的价格被成功拍下。而与此同时,上海黄金交易所中的金价是…

Three.js 官方文档学习笔记

Address:Three.js中文网 (webgl3d.cn) Author:方越 50041588 Date:2024-07-19 第一个3D案例—创建3D场景 创建3D场景对象Scene: const scene new THREE.Scene(); 创建一个长方体几何对象Geometry: const geomet…

【C++】:AVL树的深度解析及其实现

目录 前言一,AVL树的概念二,AVL树节点的定义三,AVL树的插入3.1 第一步3.2 第二步 四,AVL树的旋转4.1 右单旋4.2 左单旋4.3 右左双旋4.4 左右双旋4.5 插入代码的完整实现4.6 旋转总结 五,AVL树的验证六,实现…