数据结构之链表(单链表)

news2024/12/28 1:55:36

文章目录


前言

数据结构之顺序表中我们有讲到顺序表有一些问题和缺点,为了能解决顺序表的问题,我们引入一个新的线性表——链表

一、链表

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

链表与顺序表的不同
1.链表是按需申请空间的
2.链式结构在逻辑上是连续的但是在物理结构上不一定是连续的;两次申请的空间可能是连续的,也可能是不连续的。
注意:链表和顺序表所申请的空间都是在堆上申请的。(动态开辟的空间都是在堆上申请的)

二、链表的八种结构

1.单向或者双向

2.带头或者不带头(头:哨兵位)

3.循环或者不循环

以上三种类型,两两组合就能得到链表的八种结构,虽然有这么多种链表,但是我们最常用的还是两种:
1.无头单向非循环链表;
在这里插入图片描述
2.有头双向循环链表。
在这里插入图片描述

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

我们今天主要介绍的是无头单向非循环链表(单链表)。

三、单链表

1.接口

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;
//动态申请一个新的节点
SLTNode* BuySLTNode(SLTDataType x);
//打印单链表中的数据
void SLTPrint(SLTNode* phead);
//单链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//单链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//单链表的头删
void SLTPopFront(SLTNode** pphead);
//单链表的尾删
void SLTPopBack(SLTNode** pphead);
//单链表的查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
// 单链表在pos位置之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
// 单链表在pos位置之后插入x
void SLTInsertAfter(SLTNode* pphead, SLTNode* pos, SLTDataType x);
// 单链表删除pos位置的节点
void SLTEarse(SLTNode** pphead, SLTNode* pos);
// 单链表删除pos位置之后的节点
void SLTEarseAfter(SLTNode* pos);

2.接口的实现

1.开辟一个新的节点

(单链表是由一个节点一个节点链接起来的)

//开辟一个新的节点
SLTNode* BuySLTNode(SLTDataType x)
{
	SLTNode* p = (SLTNode*)malloc(sizeof(SLTNode));
	p->data = x;
	p->next = NULL;
	return p;

1.打印单链表

(方便调试观察)

//打印单链表中的数据
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while(cur != NULL)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

2.头插

//单链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* NewNode = BuySLTNode(x);
	NewNode->next = *pphead;
	*pphead = NewNode;
}

3.尾插

//单链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* NewNode = BuySLTNode(x);
	//单链表为空
	if ((*pphead) == NULL)
	{
		NewNode->next = *pphead;
		*pphead = NewNode;
	}
	//单链表不为空
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = NewNode;
	}
}

4.头删

//单链表的头删
void SLTPopFront(SLTNode** pphead)
{
	assert(*pphead);//如果链表为空再继续删除就会报警告
	SLTNode* cur = *pphead;
	*pphead = (*pphead)->next;
	free(cur);
}

5.尾删

//单链表的尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(*pphead);//如果链表为空再继续删除就会报警告
	SLTNode* cur = *pphead;
	if ((cur->next) != NULL)
	{
		SLTNode* prev = cur->next;
		while (prev->next != NULL)
		{
			prev = prev->next;
			cur = cur->next;
		}
		free(prev);
		cur->next = NULL;
	}
	else
	{
		free(cur);
		*pphead = NULL;
	}
}

6.单链表的查找

//单链表的查找(找到了返回该节点的地址)
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* p = phead;
	while (p)
	{
		if (p->data == x)
		{
			return p;
		}
		p = p->next;
	}
	assert(p);
}

7.在pos位置之前插入数据

// 单链表在pos位置之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	SLTNode* prev = *pphead;
	SLTNode* NewNode = BuySLTNode(x);
	if (prev == pos)
	{
		NewNode->next = prev;
		*pphead = NewNode;

	}
	else
	{
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		NewNode->next = prev->next;
		prev->next = NewNode;
	}
}

8.在pos位置之后插入数据

// 单链表在pos位置之后插入x
void SLTInsertAfter(SLTNode* phead, SLTNode* pos, SLTDataType x)
{
	SLTNode* prev = phead;
	SLTNode* NewNode = BuySLTNode(x);
	while (prev)
	{
		if (prev == pos)
		{
			NewNode->next = prev->next;
			prev->next = NewNode;
			break;
		}
		prev = prev->next;
	}
}

9.删除pos位置的数据

// 单链表删除pos位置的节点
void SLTEarse(SLTNode** pphead, SLTNode* pos)
{
	SLTNode* prev = *pphead;
	if (prev == pos)
	{
		*pphead = prev->next;
		free(prev);
		prev = *pphead;
	}
	else
	{
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

10.删除pos位置之后的数据

// 单链表删除pos位置之后的节点
void SLTEarseAfter( SLTNode* pos)
{
	SLTNode* cur = pos;
	assert(cur->next);//当pos指向链表最后一个节点时,不能删除pos后的值
	cur = cur->next;
	pos->next = cur->next;
	free(cur);
}

3.主函数(测试)

主要用于测试单链表实现的接口,大家也可以自行调整,代码如下:

void test1()//测试:头插
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 6);//注意是传plist的地址过去(要改变plist的值就要传plist的地址,因为形参只是实参的一份临时拷贝,如果只是传plist过去就只是将phead这个plist的临时拷贝值进行改变,)
	SLTPushFront(&plist, 16);
	SLTPushFront(&plist, 62);
	SLTPushFront(&plist, 63);
	SLTPushFront(&plist, 46);
	SLTPushFront(&plist, 67);
	SLTPrint(plist);
}
void test2()//测试:尾插
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 16);
	SLTPushBack(&plist, 62);
	SLTPushBack(&plist, 63);
	SLTPushBack(&plist, 46);
	SLTPushBack(&plist, 67);
	SLTPrint(plist);
}
void test3()//测试:头删
{

	SLTNode* plist = NULL;
	SLTPushBack(&plist, 16);
	SLTPushBack(&plist, 62);
	SLTPushBack(&plist, 63);
	SLTPushBack(&plist, 46);
	SLTPushBack(&plist, 67);
	SLTPopFront(&plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPopFront(&plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
}
void test4()//测试:尾删
{

	SLTNode* plist = NULL;
	SLTPushBack(&plist, 16);
	SLTPushBack(&plist, 18);
	SLTPushBack(&plist, 16);
	SLTPushBack(&plist, 56);
	SLTPushBack(&plist, 78);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPopBack(&plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
}
void test5()//测试:查找
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 26);
	SLTPushBack(&plist, 18);
	SLTPushBack(&plist, 16);
	SLTPushBack(&plist, 56);
	SLTPushBack(&plist, 78);
	SLTNode* ret = SLTFind(plist, 56);
	if (ret)
	{
		printf("%p\n",ret);
	}
}
void test6()//测试:在pos位置之前插入数据
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 26);
	SLTPushBack(&plist, 18);
	SLTPushBack(&plist, 16);
	SLTPushBack(&plist, 56);
	SLTPushBack(&plist, 78);
	SLTPrint(plist);
	//SLTNode* pos = SLTFind(plist, 26);
	//SLTInsert(&plist, pos, 55);//相当于头插
	//SLTNode* pos = SLTFind(plist, 16);
	//SLTInsert(&plist, pos, 55);//正常插入
	//SLTNode* pos = SLTFind(plist, 88);//pos不是链表中的数据,会被SLTFind检测出来不会进行插入
	//SLTInsert(&plist, pos, 98);//正常插入
	SLTInsert(&plist,NULL, 55);//相当于尾插
	SLTPrint(plist);
}
void test7()//测试:插入(在pos位置之后插入)//不可能进行头插
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 26);
	SLTPushBack(&plist, 18);
	SLTPushBack(&plist, 16);
	SLTPushBack(&plist, 56);
	SLTPushBack(&plist, 78);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 78);//相当于尾插
	SLTInsertAfter(plist, pos, 55);
	SLTInsertAfter(plist, plist, 55);
	SLTPrint(plist);
}
void test8()//测试:删除pos位置的数据
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 26);
	SLTPushBack(&plist, 18);
	SLTPushBack(&plist, 16);
	SLTPushBack(&plist, 56);
	SLTPushBack(&plist, 78);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 78);//相当于尾删
	SLTEarse(&plist, pos);
	SLTPrint(plist);
}
void test9()//测试:删除pos位置之后的数据
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 26);
	SLTPushBack(&plist, 18);
	SLTPushBack(&plist, 16);
	SLTPushBack(&plist, 56);
	SLTPushBack(&plist, 78);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 56);
	SLTEarseAfter(pos);
	SLTPrint(plist);
}

int main()
{
	//test1();
	//test2();
	//test3();
	//test4();
	//test5();
	//test6();
	//test7();
	//test8();
	test9();
	return 0;
}

总结

以上就是今天要讲的内容,本文介绍了线性表中的链表,主要实现了单链表(无头不循环单链表),大家感兴趣的也可以根据作者所写思路(注释)自行实现单链表。
本文作者也是一个正在学习编程的萌新,目前也只是刚开始接触数据结构这方面的内容,如果有什么内容方面的错误或者不严谨,欢迎大家在评论区指出。
最后,如果本篇文章对你有所启发的话,也希望可以支持支持作者,谢谢大家!
在这里插入图片描述

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

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

相关文章

MySQL8.0概述及新特性

文章目录学习资料常见的数据库管理系统排名(DBMS)SQL的分类DDL:数据定义语言DML:数据操作语言DCL:数据控制语言MySQL8.0新特性性能优化默认字符集DDL的原子化计算列宽度属性窗口函数公用表表达式索引新特性支持降序索引…

面试了20+前端大厂,整理出的面试题

事件是什么?事件模型? 事件是用户操作网页时发生的交互动作,比如 click/move, 事件除了用户触发的动作外,还可以是文档加载,窗口滚动和大小调整。事件被封装成一个 event 对象,包含了该事件发生…

RabbitMQ Windows 安装、配置、使用 - 小白教程

1、配套文件 下载erlang:http://www.erlang.org/downloads/ 下载RabbitMQ:http://www.rabbitmq.com/download.html 2、RabbitMQ服务端代码是使用并发式语言Erlang编写的,安装Rabbit MQ的前提是安装Erlang,双击otp_win64_21.1.ex…

计算机毕业设计springboot+vue+elementUI汽车车辆充电桩管理系统

项目介绍 随着我国汽车行业的不断发展,电动汽车已经开始逐步的领导整个汽车行业,越来越多的人在追求环保和经济实惠的同时开始使用电动汽车,电动汽车和燃油汽车最大的而不同就是 需要充电,同时我国的基础充电桩也开始遍及了大多数…

Java 异常处理

目录 一、异常的基本概念 二 、为何需要异常处理 三 、异常的处理 四 、异常类的继承架构 五 、抛出异常 5.1、程序中抛出异常 5.2、指定方法抛出异常 六 、自定义异常 不管使用的那种语言进行程序设计,都会产生各种各样的错误。 Java 提供有强大的异常处理…

商业银行普惠金融可持续发展综合能力呈现梯队化,专项领域各有所长

易观分析:普惠金融有别于传统的金融体系,强调构建包容性、公平性的金融服务生态,商业银行提升可持续发展的综合能力需关注五个方面的因素:获客能力上以普惠客群的金融需求为锚点,增强银行服务生态的多样性,…

罗正雄:基于展开交替优化的盲超分算法DAN

SFFAI 90—超分辨率专题《罗正雄:基于展开交替优化的盲超分算法》 退化表达式为: 盲超分就是已知y,求x 这个求解过程可以表示为如下最优化问题:求出使得以下表达式最小的k和x值 盲超分存在的挑战 病态:退化过程会损…

Leetcode 891. 子序列宽度之和

一个序列的 宽度 定义为该序列中最大元素和最小元素的差值。给你一个整数数组 nums ,返回 nums 的所有非空 子序列 的 宽度之和 。由于答案可能非常大,请返回对 109 7 取余 后的结果。子序列 定义为从一个数组里删除一些(或者不删除&#xf…

基于matlab目标雷达横截面建模(附源码)

目录 一、介绍 二、简单点目标 RCS 三、复杂目标RCS 四、四个散射体组成的目标进行建模 五、具有多个散射体的扩展目标的宽带RCS 六、波动目标RCS 七、偏振目标RCS 八、结论 九、程序 此示例演示如何以不断提高的保真度对雷达目标进行建模。该示例介绍了简单点目标的雷…

MYSQL索引详解和优化

索引的定义 我们在看书的时候,都知道有目录,我们可以通过目录快速的找到书中的内容,而书中的目录就是充当书的索引。在数据库中的索引也是一样的。 索引的定义: 索引是帮助存储引擎快速获取数据的一种数据结构,即数据…

flex设置为1后为什么要设置width为0,和布局超出省略号为什么会超出容器,为什么会没有用

前言 最近在做手机端的页面,制作过程出现了flex布局的一些问题,再次记录在解决办法关于在flex:1的情况下设置为width的效果 如果没有设置width,当内部元素的内容大小超过平均分配的剩余空间时,元素的宽度等于内容大小,如果设置了width并且这个width的大小小于平均分配的剩余空…

[Linux] 如何查看内核 Kernel 版本(查多个Kernel的方法)

上图来源于:turnoff.us,描述了Linux内核结构,有兴趣的同学可以访问原址看看 文章目录什么是Linux内核查看Linux内核Kernel的场景情况查看 Kernel 的几种方式1、使用 uname2、使用 hostnamectl 命令3、查看 /proc/version4、使用 rpm 命令5、使…

2.9 场景式文案,原来是这样子写的【玩赚小红书】

人的生活,就是一个场景连着另一个场景,循环往复,朝朝暮暮。 文案,只要切入了用户的场景,就可以切入他的生活,进而切入他的心。 什么是「 场景化文案」 ?可以看一组对比: 非场景文…

Tomcat的概述、部署、优化

文章目录一、Tomcat概述1、Tomcat的概念2、Tomcat的核心组件3、Java Servlet 的概念4、JSP的概念5、Tomcat顶层架构6、Container 结构分析:7、Tomcat请求过程二、Tomcat服务部署1、Tomcat服务部署的步骤1.1 关闭防火墙,将安装 Tomcat 所需软件包传到/opt目录下1.2 安…

C语言第十课(上):编写井字棋游戏(综合练习1)

目录 前言: 一、文件建立: 1.头文件game.h: 2.函数定义文件game.c: 3.工程测试文件test.c: 二、编写井字棋游戏: 1.程序整体执行思路: 2.menu菜单函数实现: 3.game游戏函数逻辑&am…

Linux环境下基于VSCode和CMake实现C/C++开发

layout: post title: Linux环境下基于VSCode和CMake实现C/C开发 description: Linux环境下基于VSCode和CMake实现C/C开发 tag: 开发工具 文章目录Linux开发环境Linux目录结构常用指令选项ls:list directory contentscd:change directorytouch&#xff1a…

【附源码】计算机毕业设计JAVA校园讲座管理

【附源码】计算机毕业设计JAVA校园讲座管理 目运行 环境项配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: JAVA mybati…

linux驱动之mmap地址映射

应用场景 首先在linux中应用程序无法是直接访问驱动程序的数据的, 需要通过 copy_to_user 和 copy_from_user才能实现数据传输, 那么数据量大了以后如LCD的数据, 那么就会有很长的耗时, 为了解决这一问题, 引入mmap, 将底层物理地址映射出来, 让应用程序得以直接读写这一块内存…

Linux进程控制(下)--->进程程序替换

文章目录什么是进程程序替换为什么要进行进程程序替换怎么进行进程程序替换execlexecvexeclpexecvpexecleexecvpe使用c的可执行程序调用一个python脚本如何理解进程程序替换进程程序替换接口的返回值从进程独立性体会程序替换什么是进程程序替换 在讲进程程序替换之前&#xf…

[附源码]java毕业设计兰州市邮政公司新邮预订户管理信息系统

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: SSM mybatis Maven Vue 等等组成,B/S模式 M…