数据结构之单链表(不带头单向非循环链表)

一.引言

上一节我们学过了顺序表,那么我们想想顺序表有没有问题呢?我们来讨论顺序表的问题及思考。
顺序表问题:
1.中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到
200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
由于我们上一章主要放在了动态顺序表,那么我们以其为主进行讨论:
动态顺序表要求:
1、插入数据,空间不够了.要增容
2、要求数据是依次存储的
因此就会有以下问题:
1、如果空间不够,增容。 增容会付出一定性能消耗,其次可能存在一定的空间浪费
2、头部或者中部左右的插入删除效率低。 -》0(N)
如何解决:
1、空间上,按需给空间
2、不要求 物理空间连续,头部和中间的插入,就不需要挪动数据
我们引入链表概念。

二.链表介绍

概念:链表是一种 物理存储结构上 非连续 非顺序 的存储结构,数据元素的 逻辑顺序 是通过链表
中的 指针链接 次序实现的
链表分类:
实际中链表的结构非常多样,常见有以下 8 种链表结构类型:
区分规则:单向/双向         循环/不循环           带头/不带头
1.不带头 单向非循环链表
2.不带头单向循环链表
3.不带头双向不循环链
4.不带头双向循环链表
5. 带头单向非 循环链表
6. 带头单向 循环链表
7. 带头双向非 循环链表
8.带头双向循环链表
接下来本文主要讲不带头 单向非循环链表单链表,当然其他的也会在后面文章讲解:

三.不带头单向非循环链表的实现

我们还是分步骤来进行讲解:
第一步:
建立一个结构体:
//建立结构体
typedef int SLTDateType;//便于其他如char float类型转换
typedef struct SList
{
	SLTDateType date;
	struct SList* next;//注意不能写成struct SList next,这样写会一直调用该结构体,会一直循环
}SL;//重命名

这里我们再次强调一下:链表是通过地址将一个个数据联系起来的,因此我们在结构体中要有一个成员为struct类型,连接下个结构体数据,但是我们又不能直接写成结构体成员,理由里面写了,因此我们用结构体指针最好,可以直接指向下个结构体数据,正符合访址连接。

第二步:
我们开始实现接口,先实现打印接口
//打印单链表
void SListPrint(SL* phead)
{
	SL* cur = phead;//定义cur指向phead指向的地址
	while (cur != NULL)//当cur不为空指针时
	{
		printf("%d->", cur->date);//输出当前指向的结构体成员date的值
		cur = cur->next;//使cur指向下一个链表的位置
	}
	//当为NULL时
	printf("NULL\n");//结尾用NULL表示,便于理解
}

第三步:

开辟空间函数(重点)

// 动态申请一个结点
SL* BuySListNode(SLTDateType x)//注意:返回的是一个SL指针,形参为x
{
	SL* newnode = (SL*)malloc(sizeof(SL));//动态开辟一个SL结构体大小的内存空间,并且用newnode指针指向该空间
	if (newnode == NULL)//判断指向的空间是否为空
	{
		perror("malloc fail:");
		return 1;
	}
	//不为空时:
	newnode->date = x;//传值
	newnode->next = NULL;//将newnode指向的结构体中成员struct SList* next为空指针,即不指向其他位置
	return newnode;//返回该结构体地址
}

学会画图理解!!!

第四步:

实现头插,头删,尾删,尾插四个函数

我们先写个尾插函数,顺便检查前面写的有没有问题

// 单链表尾插
void SListPushBack(SL** pphead, SLTDateType x)
{
	//传的是指针的地址,因此用二级指针接收
	//开辟空间
	SL* newnode =BuySListNode(x);
	//判断*pphead是否为空指针
	if (*pphead == NULL)
	{
		*pphead = newnode;//当为空时,将newnode指向的结构体空间赋给*pphead
	}
	else//否者,进行将newnode指向的空间连接到后面
	{
		SL* tail = *pphead;
		while (tail->next!=NULL)//注意这样写可以保证tail的位置是链表的最后位置
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

接下来我们检测下:

非常完美,没问题,那么我们就快速实现其他几个函数吧!
//单链表头插
void SListPushFront(SL** pphead, SLTDateType x)
{
	//开辟空间
	SL* newnode = BuySListNode(x);
	//判断是否开辟成功
	if (newnode == NULL)
	{
		perror(newnode);
		return 0;
	}
	//成功
	newnode->next =*pphead;//将开辟的结构体成员struct SList* next指向原链表的第一个数据
	*pphead = newnode;//将原指向开头链表的指针指向newnode
}
//单链表尾删定义
void SListPopBack(SL** pphead)
{
	SL* fast = *pphead;
	//情况一:*pphead直接为空指针
	if (*pphead == NULL)
	{
		perror(*pphead);
		return 1;
	}
	//情况二:链表只有一个结构体成员
	if (fast->next==NULL)
	{
		free(*pphead);
		free(fast);
		pphead = NULL;
		return 0;
	}
	//对于尾删,我们可以利用双指针法
	//fast指针比slow指针快,这样便于留住要的地址
	SL* slow = NULL;//
	while (fast->next != NULL)//画图理解会简单点
	{
		//如果fast指针指向的结构体存在
		slow = fast;//将slow指针移动到fast位置
		fast = fast->next;//fast指针前移
	}
	//此时slow指针为链表倒数第二个值
	free(fast);//fast指针可以释放了
	slow->next = NULL;//将链表倒数第二个结构体指向NULL,防止野指针
}

尾删还是有点难度的,需要画图仔细理解一下,同时要注意考虑全面!!!

//单链表头删定义
void SListPopFront(SL** pphead)
{
	//头删步骤:1,保留链表第二个结构体,2.free第一个结构体,3.将*pphead指向第二个结构体
	SL* next = (*pphead)->next;//步骤1
	free(*pphead);//步骤二
	*pphead = next;//步骤三
}

打印观察上面的程序:

#include "SList.h"

void Test1()
{
	//调用函数
	SL* plist= NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	//打印观察
	SListPrint(plist);
	SListPushFront(&plist, 5);
	SListPushFront(&plist, 6);
	SListPushFront(&plist, 7);
	//打印观察
	SListPrint(plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	SListPopBack(&plist);
	//打印观察
	SListPrint(plist);
	SListPopFront(&plist);
	SListPopFront(&plist);
	SListPopFront(&plist);
	//打印观察
	SListPrint(plist);
}

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

结果:

可见,上述代码没有问题!
第五步:
实现一些特殊接口:查找接口,随机删除接口,随机插入接口
//单链表寻找定义
SL* SListFind(SL* phead, SLTDateType x)
{
	SL* cur = phead;//定义一个cur指针指向链表第一个结构体
	while (cur != NULL)//等价于while(cur),如果cur指向的结构体不为空
	{
		//判断关系
		if (cur->date == x)//如果相等
		{
			return cur;//返回cur指针
		}
		cur = cur->next;//否者,指针指向下一个结构体
	}
	//如果走完了还没有找到,返回NULL
	return NULL;
}
//单链表随机插入定义(本质上是在pos前插入)
void SListinsert(SL** pphead,SL* pos, SLTDateType x)
{
	//判断是否存在节点
	if (pos == *pphead)
	{
		//无节点情况
		//此时直接等价于头插
		SListPushFront(pphead,x);//这里直接写pphead,原因是因为直接传pphead,可以直接接收
	}
	else 
	{
		//插入要开辟空间
		SL* newnode = BuySListNode(x);
		SL* prev = *pphead;//定义一个指针指向链表第一个结构体
		while (prev->next != pos)//不满足
		{
			prev = prev->next;//继续
		}
		//满足,进行插入操作
		prev->next = newnode;
		newnode->next = pos;
	}
}

检查这两个:

void Test2()
{
	SL* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SL* pos = SListFind(plist, 3);
	if (pos)
	{
		SListinsert(&plist,pos,30);
		SListinsert(&plist, pos, 40);
		pos = pos->next;
		SListinsert(&plist, pos, 30);
	}
	//打印观察
	SListPrint(plist);
}
int main()
{
	Test2();
	return 0;
}

结果:

下面来实现随机删除:
//单链表随机删除定义(本质上是删除pos位置的结构体)
void SListErase(SL** pphead, SL* pos)
{
	//判断
	//如果链表无成员
	if (pos == *pphead)
	{
		//此时直接等价于头删
		SListPopFront(pphead);//注意:这里是pphead
	}
	SL* prev = *pphead;//定义一个指针指向链表第一个结构体
	while (prev->next!=pos)//判断指针的下一个节点是否为pos位置
	{
		prev = prev->next;//后移
	}
	//如果是pos位置
	prev->next = pos->next;//将指针prev指向的位置改成pos指针指向的位置
	free(pos);//释放pos指针
}

检查:

void Test2()
{
	SL* plist = NULL;
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	SL* pos = SListFind(plist, 3);
	if (pos)
	{
		SListinsert(&plist,pos,30);
		SListinsert(&plist, pos, 40);
		pos = pos->next;
		SListinsert(&plist, pos, 50);
		
	}
	//打印观察
	SListPrint(plist);
	SL* pos2 = SListFind(plist, 30);
	if (pos2)
	{
		SListErase(&plist, pos2);
	}
	//打印观察
	SListPrint(plist);
}
int main()
{
	Test2();
	return 0;
}

结果:

 no err
接下来,我们实现最后一个函数:销毁函数
//单链表销毁定义
void SListDestory(SL* phead)
{
	free(phead->next);
	phead->date = 0;
}

最后:在这里感谢大家的支持,让我们继续前行,加油!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Go语言基础知识学习(一)

Go基本数据类型 bool bool型值可以为true或者false,例子: var b bool true数值型 类型表示范围int8有符号8位整型-128 ~ 127int16有符号16位整型-32768 ~ 32767int32有符号32位整型-2147783648 ~ 2147483647int64有符号64位整型uint8无符号8位整型0 ~ 255uint16…

使用CLion进行cuda编程,并使用cuda-gdb对核函数进行debug,这可能是全网你能够找到的最详细的CLion和cuda编程环境配置教程了

文章目录 前言一、环境准备二、相关学习资料三、环境配置1.新建Clion C Executable项目2.在Clion中的ToolChains中配置cuda-gdb3.配置CMake options4.配置CMakeLists.txt(1) Failed to compute shorthash for libnvrtc.so(2) c: error: unrecognized command-line option -G(3)…

【华为数据之道学习笔记】3-2 基础数据治理

基础数据用于对其他数据进行分类,在业界也称作参考数据。基础数据通常是静态的(如国家、币种),一般在业务事件发生之前就已经预先定义。它的可选值数量有限,可以用作业务或IT的开关和判断条件。当基础数据的取值发生变…

AI PC行业深度研究报告:AI PC革新端侧AI交互体验

今天分享的人工智能系列深度研究报告:《AI PC行业深度研究报告:AI PC革新端侧AI交互体验》。 (报告出品方:华创证券) 报告共计:28页 点击添加图片描述(最多60个字)编辑 一、硬件端…

mybatis多表映射-对多关联

1、建库建表 create database mybatis-example; use mybatis-example; create table t_book (bid varchar(20) primary key,bname varchar(20),stuid varchar(20) ); insert into t_book values(b001,Java,s001); insert into t_book values(b002,Python,s002); insert into …

【SpringBoot教程】SpringBoot 实现前后端分离的跨域访问(CORS)

作者简介:大家好,我是撸代码的羊驼,前阿里巴巴架构师,现某互联网公司CTO 联系v:sulny_ann(17362204968),加我进群,大家一起学习,一起进步,一起对抗…

xtts和ogg不选择?

不选择ogg的理由: 1.需要在源端创建用户赋权,启用数据库最小日志,附加日志等操作--对生产影响较大 2.外键约束过多,割接启用可能很慢https://www.modb.pro/db/201126--割接停机时间影响 3.初始化配置expdp导出可能快照过旧&#x…

基于Java swing的医院信息管理系统(Java毕业设计)

大家好,我是DeBug,很高兴你能来阅读!作为一名热爱编程的程序员,我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里,我将会结合实际项目经验,分享编程技巧、最佳实践以及解决问题的方法。无论你是…

秒级监控、精准迅速:全面保障业务可用性 | 开源日报 No.101

louislam/uptime-kuma Stars: 41.1k License: MIT Uptime Kuma 是一个易于使用的自托管监控工具,主要功能和核心优势包括: 监控 HTTP(s) / TCP / HTTP(s) 关键词 / HTTP(s) Json 查询 / Ping / DNS 记录等服务的可用性提供时尚、响应迅速且良好用户体验…

docker配置连接harbor私有仓库

一、前言 以下分为两种情况说明docker对harbor私有仓库的访问配置,一种是harbor使用自建证书配置https,一种是使用公有证书配置https 二、docker配置 harbor使用自建证书的情况 使用自建证书对harbor进行https配置,docker会将该仓库识别成不…

客服工单系统推荐:哪个最适合您?

客服工单系统是企业的业务过程的“保安”,保障业务流程的顺利开展,同时保障企业客户的权益。所以,市场上有越来越多的企业纷纷配置了客服工单系统,以提供客户服务质量。 对于有购买意向的中小企业来讲,需要关注哪些因…

pycharm中py文件设置参数

在py文件中右键 直接对应复制进去即可

供应链管理痛点大解析!内附解决方案

供应链是指涉及产品或服务生产、运输、分销和最终交付给客户的过程。 用一个汽车制造的例子来帮助大家理解: 原材料采购: 汽车制造商需要从供应商处采购制造汽车所需的原材料,例如金属、橡胶、塑料和玻璃。生产制造:获得原材料&…

关于mysql高版本使用groupby导致的报错

在开发时,遇到mysql版本在5.7.X及以上版本时使用group by 语句会报以下的错误 Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column business_typ…

班级管理的重要性

班级管理,就像是一座桥,连接着学生和老师,它的重要性不言而喻。 营造良好的学习氛围 班级管理不仅仅是维护秩序,更是营造一个积极向上的学习氛围。一个好的班级管理,能让学生更加专注于学习,提高学习效率。…

Stable Diffusion WebUI训练Lora测试XYZ显示例图

方式一 1.1 选择模型放入目录 将模型放入sd项目的models\Lora\目录,尽量保持和其他模型分开。 sd中显示如下: 1.2 脚本X/Y/Zplot选择 X轴类型:提示词搜索/替换 X轴值:NUM,000001,000002, 000003, 000004, 000005, 000006, 000007, 000008, 000009, 000010 Y轴类型:提…

[MySQL] SQL优化之性能分析

🌈键盘敲烂,年薪30万🌈 目录 一、索引优化 1、索引是什么: 2、索引的数据结构: 3、索引种类: 4、sql分析(回表查询) 二、定位慢查询语句 1、慢查询日志 2、profile详情 3、…

数字化合纵连横:跨境电商的多维商业布局

跨境电商在数字化时代崛起,不再局限于传统的边界,而是通过数字化手段实现了合纵连横的多维商业布局。这种布局不仅推动了国际贸易的创新,也为企业在全球范围内发展提供了更为广阔的空间。本文将深入探讨跨境电商如何通过数字化手段实现合纵连…

Android:The emulator process for AVD Pixel_2_API_29 was killed

The emulator process for AVD Pixel_2_API_29 was killed 报错描述: 第一次安装Android studio好不容易解决gradle启动模拟器又出现了以下错误 The emulator process for AVD Pixel_2_API_29 was killed原因一: 需要安装Intel x86 Emulator Acceleer…

【C语言快速学习基础篇】之二控制语句、循环语句

文章目录 一、控制语句1.1、if...else...单条件语句1.2、if...else if...else...多条件语句1.3、switch...case 二、循环语句2.1、for循环2.2、while循环2.3、注意:for循环和while循环使用上面等同2.4、do while循环2.4.1、while条件成立时2.4.2、while条件不成立时…