《单链表》的实现(不含哨兵位的单向链表)

目录

​编辑

前言:

 链表的概念及结构:

链表的实现:

1.typedef数据类型:

2.打印链表 :

3.创建新节点:

4.尾插 :

5.头插:

6.尾删 :

7.头删:

8.查找节点:

9.指定下标前插入:

10.删除当前下标 

11. 指定下标后插入:

12.删除当前下标的后一个节点 :

13.销毁链表:

总结:


前言:

我们在前面的学习中深度的讲解了顺序表的模拟实现,而在上一篇好题分享中,我们又对于链表中的几道基础题(含有含金量)作出了完善的解析,今天我们将要真正的开启链表的学习,就从最基础的模拟实现一个单链表开始

前两篇的blog在这里:

好题分析(2023.10.29——2023.11.04)-CSDN博客

《动态顺序表》的实现-CSDN博客

 链表的概念及结构:

链表(Linked List)是一种常见的数据结构,它是由一系列节点(Node)组成的,每个节点包含两个部分:数据域和指针域。数据域存储数据,指针域指向下一个节点。

链表可以分为单向链表和双向链表两种:

单向链表:每个节点只有一个指针域,指向下一个节点。

双向链表:每个节点有两个指针域,一个指向前一个节点,一个指向后一个节点。

链表相较于数组,具有以下优势:

  1. 内存空间可以动态分配,不需要预先定义大小。

  2. 插入和删除操作比较容易,只需要改变指针指向即可,不需要移动元素。

  3. 可以节省内存空间,因为链表中的节点可以零散分布在内存中,不需要连续的空间。

但是链表的缺点是:

  1. 访问元素的时间复杂度为O(n),而数组可以通过下标随机访问元素,时间复杂度为O(1)。

  2. 链表的节点需要额外的指针域来存储指向下一个节点的指针,因此内存占用相对于数组较大。

这些特点使得链表在某些场景下比数组更加适用,比如实现队列、栈、哈希表等数据结构,或者需要频繁插入和删除元素的场景。

介绍完链表的基本信息,下面我们就来实现链表!

链表的实现:

1.typedef数据类型:

typedef int SLNDataType;

typedef struct SlistNode
{
	SLNDataType val;
	struct SlistNode* next;
}SLNode;

这里与我们最早实现通讯录和顺序表的结构相似,在这里我就不过多赘述。

2.打印链表 :

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

打印函数的内容较好理解,在这里我不进行过多的赘述。

3.创建新节点:

 

SLNode* CreateNode(SLNDataType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("CreateNode -> malloc");
		exit(-1);
	}
	newnode->next = NULL;
	newnode->val = x;
	return newnode;
}

这里与我们在顺序表中,在堆区申请空间而使用的malloc相似。

在这里我们是定义了一个指针newnode,这里要注意的是此时的newnode的返回值是SLNode*,意思就是指向一个SLNode类型的结构体指针! 

所以对于该结构体我们就给它赋予初始值,即newnode->val = x;newnode->next = NULL;

4.尾插 :

void SLTPushBack(SLNode** pphead, SLNDataType x)
{
	assert(pphead);
	SLNode* newnode = CreateNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;

	}
}

对于此函数的实现,我们首先要讲解的就是此时的形参必须是一个二级指针!

我们都知道,

形参是实参的一份临时拷贝!

 我们在实现这些关于结构体的函数,传递的都是结构体的地址。

对于顺序表和通讯录,我们这样传递不成问题。例:

Contact c;

CreateContact(&c);

等等代码。因为我们一开始是创建了一个结构体C,这时我们想要在结构体里修改各个成员的值,就需要调用CreateContact函数,那么我们都知道要传入结构体指针,因为当我们传入指针,指针可以在原来的数据上进行修改,同时可以返回到修改完的数据。

但当我们仅仅传一个C,而非指针时,此时函数内部就会再创建一个结构体,对新创建的结构体里的各个成员进行修改,一旦函数结束,此结构体就会销毁,原来的结构体不会发生改变。

这就是为什么形参是实参的一份临时拷贝!

同理,

对于当前的链表,我们在主函数肯定会有:

SLNode* plist = NULL;

 

我们在此刻是使用的结构体指针,而非结构体对象,因为我们使用结构体指针可以更方便访问链表的头结点,进而进行许许多多的操作。

那如果我们在函数里用的是一级指针的话,就会出现:

当我们实施添加节点时, 

 

看似我们创建了新节点,但实际上是这样的。

一旦函数结束,phead就会销毁,新开辟出来的结点就会找不到。

就会导致内存泄漏。

所以正确的做法就是利用二级指针,来解引用得到原指针而修改里面的next,并且指向新节点。

即:

 

 

 这就很好的解释了为什么要用双指针来接收。

然后就是要注意,当指针为NULL和指针已经指向节点要分开讨论。

5.头插:

void SLTPushFront(SLNode** pphead, SLNDataType x)
{
	assert(pphead);
	SLNode* newnode = CreateNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

头插的代码很好理解,在这里我不做过多赘述。

6.尾删 :

void SLTPopBack(SLNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLNode* tail = *pphead;
	if (tail->next == NULL)
	{
		free(tail);
		tail = NULL;
	}
	else
	{
		SLNode* prev = NULL;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		prev->next = NULL;
		free(tail);
		tail = NULL;
	}
	
}

对于该函数的实现,注意的就是要保存最后一个节点前的结点,这样我们就可以将前一个节点的next指向NULL。

同时注意要将空节点和有节点分开讨论。

7.头删:

void SLTPopFront(SLNode** pphead)
{
	assert(*pphead);
	assert(pphead);
	SLNode* tmp = (*pphead)->next;
	free(*pphead);
	*pphead = tmp;
}

头删的大致思路与尾删类似,在这里我不过多赘述。


8.查找节点:

SLNode* SLTFind(SLNode* phead, SLNDataType x)
{
	assert(phead);
	while (phead)
	{
		if (phead->val == x)
		{
			return phead;
		}
		phead = phead->next;
	}
	return NULL;
}

9.指定下标前插入:

void SLTInsertBefore(SLNode** pphead, SLNode* pos, SLNDataType x)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	
	SLNode* tail = *pphead;
	if ((*pphead) == NULL)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		while (tail->next != pos)
		{
			tail = tail->next;
		}
		SLNode* newnode = CreateNode(x);
		newnode->next = pos;
		tail->next = newnode;
	}
}

10.删除当前下标 

void SLTErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	SLNode* tmp = *pphead;

	if (*pphead == pos)
	{
		SLTPopBack(pphead);
	}

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

		tmp->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

11. 指定下标后插入:

void SLTInsertAfter(SLNode* pos, SLNDataType x)
{
	assert(pos);
	SLNode* newnode = CreateNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

12.删除当前下标的后一个节点 :

 

void SLTEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLNode* tmp = pos->next;
	pos->next = tmp->next;
	free(tmp);
	tmp = NULL;
}

13.销毁链表:

void SLTDestory(SLNode** pphead)
{
	assert(pphead);
	SLNode* prev = NULL;
	while (*pphead)
	{
		prev = *pphead;
		*pphead = (*pphead)->next;
		free(prev);
	}
	prev = NULL;
}

总结:

 在这里我仅仅只是讲解了实现单链表的基本概念,而对于后续的一些函数实现,我并没有进行过多的讲解,因为这些函数的实现与之前的尾插和头插大差不差。

需要注意的就是释放和插入的next指向,以及先后顺序。

这些在之后做题中会多次提到,我们也会不断熟练。

总之,对于这部分链表问题,我们应当多多练习多多刷题,提升我们的熟练度才是首要目标!

记住

“坐而言不如起而行”

Action speake louder than words

以下是我本次文章的全部代码:

Data structures amd algorithms: 关于数据结构和算法的代码 - Gitee.com

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

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

相关文章

刚安装的MySQL使用Navicat操作数据库遇到的问题

刚安装的MySQL使用Navicat操作数据库遇到的问题 一、编辑连接保存报错二、打开数据表很慢三、MySQL的进程出现大量“sleep”状态的进程四、执行sql脚本报错,部分表导不进去五、当前MySQL配置文件 一、编辑连接保存报错 连接上了数据库,编辑连接保存报错…

winform打包默认安装路径设置

点击安装程序的 Application Folder 修改属性中的 DefaultLocation

竞赛选题 深度学习疲劳驾驶检测 opencv python

文章目录 0 前言1 课题背景2 实现目标3 当前市面上疲劳驾驶检测的方法4 相关数据集5 基于头部姿态的驾驶疲劳检测5.1 如何确定疲劳状态5.2 算法步骤5.3 打瞌睡判断 6 基于CNN与SVM的疲劳检测方法6.1 网络结构6.2 疲劳图像分类训练6.3 训练结果 7 最后 0 前言 🔥 优…

云效流水线docker部署 :node.js镜像部署VUE项目

文章目录 引言I 流水线配置1.1 项目dockerfile1.2 Node.js 镜像构建1.3 docker 部署引言 云效流水线配置实现docker 部署微服务项目:https://blog.csdn.net/z929118967/article/details/133687120?spm=1001.2014.3001.5501 配置dockerfile-> 镜像构建->docker部署。 …

linux中使用arthas进行jvm内存分析

1. 安装下载 首先在官方github地址选择合适的版本,下载后上传到对于服务器。 使用unzip arthas-bin.zip 解压文件。进入目录中,执行./install-local.sh进行安装。执行完成后提示succeed,即可使用。 2. 启动 进入目录,执行java…

【优选算法系列】【专题七分治】第一节.75. 颜色分类和912. 排序数组

文章目录 前言一、颜色分类 1.1 题目描述 1.2 题目解析 1.2.1 算法原理 1.2.2 代码编写二、排序数组 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 2.2.2 代码编写总结 前言 一、颜色分类 1.1 题目描述 描述&…

续:将基于Nasm汇编的打字小游戏,移植到DOSBox

续:将基于Nasm汇编的打字小游戏,移植到DOSBox 文章目录 续:将基于Nasm汇编的打字小游戏,移植到DOSBox前情提要细说1 编译2 程序入口3 定位段 运行体验 前情提要 上一篇:【编程实践】黑框框里的打字小游戏,但…

我的月光宝盒初体验失败了

哈哈哈,我爱docker, docker 使我自由!!! docker make me free! 菠萝菠萝蜜口号喊起来。 https://github.com/vivo/MoonBox/ windows上安装好了docker之后,docker-compose是自带的。 docker-compose -f docker-compo…

SpringBoot核心知识点总结【Spring Boot 复习】

文章目录 Spring Boot 精要1. 自动配置2. 起步依赖3. 命令行界面4. Actuator 开发SpringBoot程序1. 启动引导Spring2. 测试Spring Boot应用程序3. 配置应用程序属性2.2 使用起步依赖2.3 使用自动配置专注于应用程序功能 Spring Boot 精要 Spring Boot将很多魔法带入了Spring应…

KiB、MiB与KB、MB的区别

KiB、MiB与KB、MB的区别

智安网络|数据库入门秘籍:通俗易懂,轻松掌握与实践

在现代信息化时代,数据库已成为我们日常生活和工作中不可或缺的一部分。然而,对于非专业人士来说,数据库这个概念可能很抽象,难以理解。 一、什么是数据库? 简单来说,数据库是一个存储和管理数据的系统。它…

城市内涝积水监测,万宾科技内涝预警监测系统

每一个城市的排水体系都是一个复杂的网络系统,需要多个部分配合协调,预防城市排水管网带来安全隐患,也因此才能在一定程度上缓解城市内涝带来的安全问题。在海绵城市建设过程中不仅要解决大部分道路硬化导致的积水无法渗透等问题,…

抢抓泛娱乐社交出海新风口!Flat Ads深圳沙龙活动引爆海外市场

随着全球化进程的加速,中国的应用类APP不断走向国际市场。作为产品和服务的提供者,中国开发者围绕社交泛娱乐创新,开启直播出海、短视频出海、游戏社交出海、1V1 视频出海、音频社交出海等出海热潮。“社交、泛娱乐”融合成为行业主流发展趋势…

Redis的内存淘汰策略分析

概念 LRU 是按访问时间排序,发生淘汰的时候,把访问时间最久的淘汰掉。LFU 是按频次排序,一个数据被访问过,把它的频次 1,发生淘汰的时候,把频次低的淘汰掉。 几种LRU策略 以下集中LRU测率网上有很多&am…

TensorFlow2.0教程3-CNN

` 文章目录 基础CNN网络读取数据卷积层池化层全连接层模型配置模型训练CNN变体网络简单的深度网络添加了其它功能层的深度卷积NIN网络文本卷积基础CNN网络 读取数据 import numpy as np import tensorflow as tf import tensorflow.keras as keras import tensorflow.keras.la…

python 字典Dict

一种序列类型,使用键-值(key-value)存储,具有极快的查找速度。 目录 key的特性 创建字典 元素的访问 Get获取 修改 是否存在key 删除 删除单个 删除全部 遍历 遍历key与值 只遍历值 遍历key,value方法2 结合enumera…

『 Linux 』进程概念

文章目录 🗞️ 冯诺依曼体系结构 🗞️📃 为什么在计算机当中需要使用内存充当中间介质而不使CUP与外设直接进行交互?📃 CPU如何读取数据 🗞️ 操作系统(Operating system) 🗞️📃 操作系统如何…

OPC DA客户端工具Opc quick client使用

OPC DA客户端工具Opc quick client使用 什么是OPC OPC是工业控制和生产自动化领域中使用的硬件和软件的接口标准,以便有效地在应用和过程控制设备之间读写数据。O代表OLE(对象链接和嵌入),P (process过程),C (control控制)。 OPC服务器包括…

XSS 漏洞详解

XSS 漏洞详解 文章目录 XSS 漏洞详解漏洞描述漏洞原理漏洞场景漏洞评级漏洞危害漏洞验证漏洞利用防御方案典型案例 漏洞描述 XSS全名叫Cross Site Scripting(跨站脚本攻击)因为简写和css同名所以改名为XSS,该漏洞主要利用javascript可以控制html,css&am…

剪贴板劫持--PasteJacker的使用

启动 PasteJacker [1] Windows [2] Linux [3] Exit第一次是让我们选择要攻击针对的目标系统,这里以Windows系统为例,即我自己的物理机 因此键入 1 ,回车 [1] Download and execute a msfvenom backdoor using certutil (Web delivery Past…