数据结构第二课 -----线性表之单向链表

news2024/7/6 17:39:46

作者前言

🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂
​🎂 作者介绍: 🎂🎂
🎂 🎉🎉🎉🎉🎉🎉🎉 🎂
🎂作者id:老秦包你会, 🎂
简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂
喜欢学习C语言和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂
🎂个人主页::小小页面🎂
🎂gitee页面:秦大大🎂
🎂🎂🎂🎂🎂🎂🎂🎂
🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂


链表

  • **作者前言**
  • 动态顺序表的缺陷
  • 动态顺序表的优点
  • 链表
  • 链表的分类
    • 单向链表
    • 单项链表的操作
      • 单链表的结构体
      • 打印输出
      • 创建节点
      • 尾插数据
      • 链表头插
      • 尾删
      • 头删
      • 链表查找
      • 链表的地址前插入
      • 链表的地址后插入
      • 删除
      • 链表的其他操作

动态顺序表的缺陷

  1. 尾部插入效率还不错,但是头部 和随机删除和随机插入效率很低
  2. 容量满了就要扩容。扩容分为两种,一种为原地扩容,一种为异地扩容(效率低下),扩容一般都会存在一定的空间浪费,(一次扩大50,而使用就使用一两个)

动态顺序表的优点

  1. 连续存储说明只需要知道一个地址就可以访问剩下的元素

链表

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
在这里插入图片描述
这个图是我们想象出来的,图中的方框就是一个节点
为了解决这个问题,前面的大佬就想到了通过地址访问空间,每一个节点存放下一个节点的地址
在这里插入图片描述
用plist存放第一个节点的地址,然后通过遍历一一访问
注意:
1.从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续般都是从堆上申请出来的
2.现实中的结点一般都是在堆上申请出来的(动态开辟)
3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
服设在32位系统上,结点中值域为int类型,则一个节点的大小为8个字节,则也可能有下述链表

链表的分类

链表有8种,下面我来一一介绍

  1. 单向或者双向
    在这里插入图片描述

  2. 带头或者不带头
    在这里插入图片描述

  3. 循环或者非循环

在这里插入图片描述
在这里插入图片描述
8种链表就是这么来的

单向链表

单向链表是链表中最常用的一种,一个列表节点有两个字段,即数据域和指针域。在单向链表中,指向第一个节点的节点称为“头结点”,最后一个节点的指针域设为null。
在这里插入图片描述

单项链表的操作

单链表的结构体

typedef int SLDataType;
//链表元素
typedef struct SListNode
{
	SLDataType val;
	struct SListNode *next;
	//这里只是一个指针,大小是4/8个大小,如果是定义成结构体变量就会出错
}SListNode;

这里要注意的就是struct SListNode next不能写成SListNodenext

打印输出

思路图:
在这里插入图片描述

//输出链表内容
void SLPrint(SListNode* phead)
{
	assert(phead);
	//防止phead的值改变
	SListNode* cur = phead;
	while (cur!= NULL)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}
	printf("\n");

}

这里很容易搞混,一些写出的错误可能就是链表的结尾和地址搞不明白很容易混淆
错误写法

void SLPrint(SListNode* phead)
{
	assert(phead);
	//防止phead的值改变
	SListNode* cur = phead;
	while (cur->next!= NULL)
	{
		printf("%d ", cur->val);
		cur = cur->next;
	}
	printf("\n");
}

这种情况就会导致最后一个无法打印出来

创建节点

//创建节点(动态开辟,防止出作用域销毁)
SListNode* CreateNode(SLDataType elemest)
{
	SListNode* p = (SListNode*)malloc(sizeof(SListNode));
	if (p == NULL)
	{
		perror("CreateNode -malloc");
		return;
	}
	p->next = NULL;
	p->val = elemest;
	return p;

}

尾插数据

思路: 我们要判断传入的链表中是不是空链表,空链表就是phead =NULL,如果是空链表,我们只需要phead指向插入数据的地址就行,如果不是,我们就要找到最后一个节点,把该节点的成员next的值是该数据的地址,然后把该数据的next改为NULL

void SLPushBack(SListNode** pphead, SLDataType elemest)
{
	//创建一个节点
	SListNode* Node = CreateNode(elemest);
	//找到最后一个节点,判断phead是否是NULL
	if (*pphead)
	{
		SListNode* stall = *pphead;
		while (stall->next != NULL)
		{
			stall = stall->next;
		}
		//插入
		stall->next = Node;
	}
	else
	{
		*pphead = Node;
	}
}

这里我们要判断两种情况的过程中有一些人可能会写成以下错误
1.
在这里插入图片描述
这个错误就是让stall的地址指向的不是最后一个节点了

​2.

就是这里是传值调用,无法更改phead 的值,有很多就误以为更改这个形参就会更改实参了,所以我们要传入phead的地址,改变外面结构体的指针(phead)就要用二级指针

链表头插

思路图:
在这里插入图片描述
思路:让phead指向节点2

//头插
void SLPushFront(SListNode** pphead, SLDataType elemest)
{
	//创建节点
	SListNode* Node = CreateNode(elemest);
	//插入
	SListNode* tail = *pphead;
	*pphead = Node;
	(*pphead)->next = tail;
}

尾删

思路:
我们要分三种情况
一种是空链表 : 直接判断*phead
一种是只有一个节点 :判断(*pphead)->next 是否是 NULL
一种是多个节点:
在这里插入图片描述
思路:只要tail->next不是NULL,prev=tail,然后 tail指向下一个节点,否则free(tail),然后把prev->next =NULL

//尾删
void SLPopBack(SListNode** pphead)
{
	assert(*pphead && pphead);
	//一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else//多个节点
	{
		SListNode* prev = *pphead;
		SListNode* tail = (*pphead)->next;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail == NULL;
		prev->next = NULL;
	}
}

或者这样写

//尾删
void SLPopBack(SListNode** pphead)
{
	assert(*pphead && pphead);
	//一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else//多个节点
	{
		
		SListNode* tail = *pphead;
		while (tail->next ->next != NULL)
		{
			tail = tail->next;
		}
		free(tail ->next);
		tail->next = NULL;

	}
}

错误写法:
在这里插入图片描述
这种写法就是一个大错误,主要是分不清prev和tail都是指向同一块空间,且没有考虑一个节点的情况

头删

思路图:
在这里插入图片描述

//头删
void SLPopFront(SListNode** pphead)
{
	assert(pphead && *pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* p = (*pphead)->next;
		free(*pphead);
		*pphead = p;
	}
}

或者这样写

//头删
void SLPopFront(SListNode** pphead)
{
	assert(pphead && *pphead);
	SListNode* p = (*pphead)->next;
	free(*pphead);
	*pphead = p;
}

这里我们主要还是要区分
pphead = NULL (存储的是空地址)
*pphead = NULL (phead存储的是NULL,但是phead有地址,代表空链表)
(*pphead)->next = NULL(代表的是当前节点是尾节点)

链表查找

思路: 遍历一遍链表,找到就返回地址,找不到就返回NULL

void* SLFind(SListNode* phead, SLDataType elemest)
{
	assert(phead);
	SListNode* tail = phead;
	while (tail != NULL)
	{
		if (tail->val == elemest)
		{
			return tail;
		}
		tail = tail->next;
	}
	return NULL;
}

链表的地址前插入

这里我们要清楚,传入的地址是否为NULL
思路: 插入分两种情况,一种是头插 一种是插入到多个之间

// 插入pos前面
void SLTInsert(SListNode** pphead, SListNode* pos, SLDataType elemest)
{
	//两者不为NULL
	assert(pphead && pos && *pphead);
	SListNode* tail = *pphead;
	// 创建节点
	SListNode* newnode = CreateNode(elemest);
	if (tail == pos)
	{
		//头插
		newnode->next = pos;
		*pphead = newnode;
	}
	else
	{
		while (tail->next != pos)
		{
			tail = tail->next;
		}
		tail->next = newnode;
		newnode->next = pos;
	}
}

这里的断言主要分三种情况 一种是 *phead和pos都可以为NULL,相当于空链表插入 ,一种是两者都不为NULL,一种是同时满足,这个要看自己来规定,这里我规定的是两个不能为空

链表的地址后插入

这里我们还是一样要考虑和前面的一样,

// 插入pos后面
void SLBInsert(SListNode** pphead, SListNode* pos, SLDataType elemest)
{
	assert(pphead && pos);
	//创建节点
	SListNode* newnode = CreateNode(elemest);

	//插入
	SListNode* tail = pos->next;
	pos->next = newnode;
	newnode->next = tail;
}

删除

思路:主要分析只要第一个节点(只有一个和多个)和多个节点


//删除
void SLErase(SListNode** pphead, SListNode* pos)
{
	assert(pphead && pos && *pphead);
	//写法1
	一个节点
	//if ((*pphead)->next == NULL)
	//{
	//	if (pos == *pphead)
	//	{
	//		free(pos);
	//		pos = NULL;
	//		*pphead = NULL;
	//	}
	//}
	//else //多个
	//{
	//	SListNode* tail = *pphead;
	//	if (pos == tail)
	//	{
	//		*pphead = (*pphead)->next;
	//		free(pos);
	//		pos = NULL;
	//	}
	//	else if (pos->next == NULL)
	//	{
	//		while (tail->next != pos)
	//		{
	//			tail = tail->next;
	//			
	//		}
	//		tail->next = NULL;
	//		free(pos);
	//		pos = NULL;
	//	}
	//	else
	//	{
	//		while (tail->next != pos)
	//		{
	//			tail = tail->next;
	//		}
	//		if (pos = tail->next)
	//		{
	//			SListNode* prev = pos->next;
	//			tail->next = prev;
	//			free(pos);
	//			pos = NULL;
	//		}
	//	}
	//}



	//写法2

	SListNode* tail = *pphead;
	if (pos == tail)
	{
		//头删
		SLPopFront(pphead);
	}
	else
	{
		//这个无法释放第一个节点
		while (tail->next != pos)
		{
			tail = tail->next;
		}
		tail->next = pos->next;
		free(pos);
		pos = NULL;
	}
	
}

链表的其他操作

//不传head ,在pos后面插入
void SLTInsertAfter(SListNode* pos, SLDataType elemest)
{
	assert(pos);
	//创建节点
	SListNode* newnode = CreateNode(elemest);
	newnode->next = pos->next;
	pos->next = newnode;
}
//不传head ,在pos前面插入,(交换值)
void SLTInsertFort(SListNode* pos, SLDataType elemest)
{
	assert(pos);
	//创建节点
	SListNode* newnode = CreateNode(elemest);
	newnode->next = pos->next;
	pos->next = newnode;
	//交换值
	SLDataType val = newnode->val;
	newnode->val = pos->val;
	pos->val = val;
}

//不传head ,在pos后面删除
void SLTEraseAfter(SListNode* pos)
{
	//删除尾节点会报错
	assert(pos &&pos->next);
	
	SListNode* prev = pos->next->next;
	free(pos->next);
	pos->next = prev;
}

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

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

相关文章

以太网和局域网

计算机网络的定义 计算机网络是一个将分散的、具有独立功能的计算机,通过通信设备与线路连接起来,由根据协议编写的软件来实现的资源共享和信息传递的系统 计算机网络的分类 广域网是互联网的核心部分 局域网 常见的局域网拓扑结构有4大类&#xff1a…

股市助手:实时股市快讯,真人语音播报,助您第一时间获取最新资讯(自己写的分享给需要的人)

文章目录 📖 介绍 📖🏡 使用环境 🏡📒 使用方法 📒📝 软件设置📝 软件运行 📖 介绍 📖 给大家分享一款自己写的软件《股市助手》,老规矩&#xff…

Windows安装docker地址流程配截图,附网卡被禁用处理(有线插了没反应)

Windows安装docker流程配截图 Windows安装docker比较简单,跟着步骤一步一步操作就行,安装包到官网下载就行 安装包下载 下载地址 https://www.docker.com/get-started/下载后双击打开,进入安装界面。单选框是添加桌面快捷方式&#xff0c…

【汇编】计算机的组成

文章目录 前言一、计算机的基本组成1.1 中央处理器(CPU)1.2 内存指令和数据存储的位置计算机中的存储单元计算机中的总线地址总线数据总线控制总线 1.3 输入设备和输出设备1.4 存储设备 二、计算机工作原理三、计算机的层次结构总结 前言 计算机是现代社…

Leetcode 【1334. 阈值距离内邻居最少的城市】

有 n 个城市,按从 0 到 n-1 编号。给你一个边数组 edges,其中 edges[i] [fromi, toi, weighti] 代表 fromi 和 toi 两个城市之间的双向加权边,距离阈值是一个整数 distanceThreshold。 返回能通过某些路径到达其他城市数目最少、且路径距离…

【C++】——继承和派生

🎃个人专栏: 🐬 算法设计与分析:算法设计与分析_IT闫的博客-CSDN博客 🐳Java基础:Java基础_IT闫的博客-CSDN博客 🐋c语言:c语言_IT闫的博客-CSDN博客 🐟MySQL&#xff1a…

详细的完美转发

不要假装努力&#xff0c;结果不会陪你演戏。文章目录 完美转发的使用场景完美转发 完美转发的使用场景 请看下面的这个代码 #include<iostream> using namespace std; void func(int&& t) {cout<<"int&&"<<endl;return; } void…

易云维®医院能源管理系统提供多方案实现医院节能计划

德国卫生部长卡尔劳特巴赫采访时说&#xff1a;“如果我们不赶紧采取有效措施&#xff0c;就会&#xff08;有医院&#xff09;倒闭。” 2022年的德国面临能源危机和通胀挑战&#xff0c;医院系统面临的人员和资金压力再次敲响警钟&#xff0c;正陷入举步维艰的处境。德国医院…

LTspice导入spice模型

一、创建原理图&#xff08;原件的样子&#xff09; 1、下载spice模型 2、用LTspice打开spice模型 3、选中模型名称&#xff0c;选择创建 4、可以自己画模型 导入后都是方块的&#xff0c;可以自己画模型的样子&#xff0c;所有引脚和模型名称都跟器件一样可以移动 da 画模…

突然消失的桌面文件如何恢复?详细教程让你轻松解决问题!

桌面文件突然消失&#xff0c;对于很多人来说&#xff0c;可能是个令人头疼的问题。这些文件可能包含重要的信息&#xff0c;也可能是数日甚至数周的努力成果。那么&#xff0c;当这种情况发生时&#xff0c;我们如何恢复丢失的文件呢&#xff1f;本文将提供一些实用的建议。 1…

mysql之主从复制和读写分离

一、主从复制 1、定义 主mysql上的数据&#xff08;新增或修改库、表里的数据&#xff09;都会同步到从mysql上 2、mysql的主从复制模式&#xff08;面试题&#xff09; &#xff08;1&#xff09;异步复制&#xff08;常用&#xff09;&#xff1a;默认的复制模式。客户端…

巨量千川「全域推广」指南来袭!助力商家开拓新流量

如今&#xff0c;在抖音上进行直播销售的商家&#xff0c;都希望在不影响ROI的情况下&#xff0c;提高整体业务水平&#xff0c;实现高效率的结果。然而&#xff0c;考虑到人货场波动和直播本身的复杂性&#xff0c;许多商家面临着诸如低投放效果、波动的ROI和缺乏GMV增长动力等…

Git企业开发级讲解(一)

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、Git初识1、提出问题2、如何解决--版本控制器3、注意事项 二、Git 安装1、Linux-centos2、…

鱼子酱产业分析:预计2029年将达到5.8亿美元

随着生活水平的提高和人们对美食品味的追求&#xff0c;鱼子酱在各个国家和地区的需求不断上升。尤其是在欧洲、俄罗斯、东亚以及北美地区&#xff0c;鱼子酱市场发展较为成熟&#xff0c;拥有众多忠实的消费者群体。此外&#xff0c;鱼子酱在亚洲市场的受欢迎程度也逐渐上升&a…

【C++初阶(七)】类和对象(下)

本专栏内容为&#xff1a;C学习专栏&#xff0c;分为初阶和进阶两部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握C。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;C &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&…

云原生之使用Docker部署home-page个人导航页

云原生之使用Docker部署home-page个人导航页 一、home-page个人导航页介绍二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、下载home-page镜像五、部署home-page导航页5.1 创建挂…

论文精读 MediaPipe Hands

MediaPipe Hands:On-device Real-time Hand Tracking MediaPipe手势&#xff1a;设备上的实时手势跟踪 论文地址&#xff1a;2006.10214.pdf (arxiv.org) 源码地址&#xff1a;GitHub - vidursatija/BlazePalm: PyTorch 目录 摘要 介绍 架构 BlazePalm Detector Hand L…

振南技术干货集:深入浅出的Bootloader(1)

注解目录 1、烧录方式的更新迭代 1.1 古老的烧录方式 (怀旧一下&#xff0c;单片机高压烧录器。) 1.2 ISP 与ICP 烧录方式 (还记得当年我们玩过的 AT89S51?) 1.3 更方便的 ISP 烧录方式 1.3.1串口 ISP &#xff08;是 STC 单片机成就了我们&#xff0c;还是我们成就了…

Python 列表List数据复杂操作

一、将列表数据每2个取一个数据添加到新列表中 prov_code [130100000000, 石家庄市, 130200000000, 唐山市, 130300000000, 秦皇岛市, 130400000000,邯郸市,130500000000, 邢台市, 130600000000, 保定市, 130700000000, 张家口市, 130800000000,承德市,130900000000, 沧州市, …

基于51单片机的智能窗控制系统设计

**单片机设计介绍&#xff0c; 基于51单片机的智能窗控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于51单片机的智能窗控制系统通常是指通过单片机控制窗户的开关和调节&#xff0c;在实现基本的开关功能的同时&…