数据结构--单链表实现

news2024/11/26 22:48:35

                                                        欢迎光顾我的homepage

前言        

        链表和顺序表都是线性表的一种,但是顺序表在物理结构和逻辑结构上都是连续的,但链表在逻辑结构上是连续的,而在物理结构上不一定连续;来看以下图片来认识链表与顺序表的差别

这里以动态顺序表为例,和链表中的单链表对比一下

动态顺序表

单链表

        这里就可以很清晰的看到顺序表的底层其实就是一个数组,数据的是连续存储的(顺序表物理结构连续);而链表它每一个数据都不是连续的(链表物理结构上不一定连续)。

链表节点

        通过观察上图,我们会发现链表每一个节点都存放在数据和下一个节点的地址。

        那么来想一下,为了链表每一个节点都统一起来,都存储有效数据和下一个节点的地址,我们就需要创建应该结构体,来存储有效数据和下一个节点的指针;
注:这里只是单链表

typedef int SLType;
typedef struct SLTNode
{
	SLType data;
	struct SLTNode* next;
}SLT;

创建好链表节点,接下来就来实习单链表存储数据的这些功能。

单链表实现

先来看一下单链表都实现都哪些功能

//输出链表
void SLTPrint(SLT* phead);
//创建节点
SLT* SLTCreat(SLType x);
//单链表头插
void SLTPushFront(SLT** pphead, SLType x);
//单链表尾插
void SLTPushBack(SLT** pphead, SLType x);
//单链表头删
void SLTPopFront(SLT** pphead);
//单链表尾删
void SLTPopBack(SLT** pphead);
//查找数据
SLT* SLTFind(SLT* phead, SLType x);
//指定位置之前插入
void SLTInsert(SLT** pphead, SLT* pos, SLType x);
//指定位置之后插入
void SLTInsertAfter(SLT* pos, SLType x);
//删除指定节点
void SLTErase(SLT** pphead, SLT* pos);
//删除指定位置后一个节点
void SLTEraseAfter(SLT* pos);

创建节点

        这里创建节点,还是使用动态内存来创建,创建完成后,将数据存储进去,并把新节点的下一个节点置为NULL

代码如下:

//创建节点
SLT* SLTCreat(SLType x)
{
	SLT* newnode = (SLT*)malloc(sizeof(SLT));
	assert(newnode);
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

        测试一下代码是否正确

可以看到代码没有问题。

输出链表

        由于这里实现单链表,存储的是整型数据,就以整型的方式输出,若存储其他类型的数据,就以存储类型的方式输出。

输出链表,首先就要遍历链表,因为链表最后一个节点里存储的下一个节点的地址为空(即最后一个节点  ->next 为空)所以,遍历链表结束的条件就是ptail ==NULL,没输出一个就让ptail指向下一个节点,直到为空,遍历结束。

        来写代码实现:

//输出链表
void SLTPrint(SLT* phead)
{
	SLT* ptail = phead;
	while (ptail!= NULL)//也可以直接写成 ptail
	{
		printf("%d -> ", ptail->data);
		ptail = ptail->next;
	}
	printf("NULL\n");
}

这里先创建两个节点并存储数据输出看一下结果

int main()
{
	SLT* plist = SLTCreat(1);
	plist->next = SLTCreat(2);
	SLTPrint(plist);
	return 0;
}

        这里也成功输出插入的两个数据。(最后一个节点的next指向空)

单链表头插

        在链表头部插入数据,不用像顺序表那样去移动所以的有效数据,链表只需要改变一个指针的指向即可

假设现在链表中已经存在两个数据,再进行头插,这时就只需要改变plist的指向即可,当然新节点的next指针也要指向下一个节点(plist指向的节点)。

代码如下:

//单链表头插
void SLTPushFront(SLT** pphead, SLType x)
{
	assert(pphead);
	SLT* newnode = SLTCreat(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

还有一种情况,如果现在链表中没有数据,再进行头插,这里代码也能实现链表没有数据时的头插

单链表尾插

        链表的尾插,因为指针传的是指向头节点的指针的地址,所以,我们需要先遍历链表,找到链表的尾部,再修改尾节点的next指针指向。

假设现在链表中已经存在两个数据,再进行尾插,此时我们只需要找到链表的尾部,修改尾节点next指针指向即可,代码如下

//单链表尾插
void SLTPushBack(SLT** pphead, SLType x)
{
	assert(pphead);
	SLT* newnode = SLTCreat(x);
	SLT* ptail = *pphead;
	//遍历链表
	while (ptail->next)
	{
		ptail = ptail->next;
	}
	ptail->next = newnode;
}

        考虑了这种情况,再来看以下链表为空的情况,如果链表为空,这里ptail->next就对空指针进行解引用操作了;所以,我们需要判断链表是否为空?如果为空,插入的新节点就是头节点,直接让plist(即*pphead)指向新节点即可;如果不为空就正常进行尾插。

最终代码如下:

//单链表尾插
void SLTPushBack(SLT** pphead, SLType x)
{	
	assert(pphead);
	SLT* newnode = SLTCreat(x);
	if (*pphead == NULL) //判断链表是否为空
	{
		*pphead = newnode;
	}
	else {
		SLT* ptail = *pphead;
		//遍历链表
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}

这里代码可以正常进行尾插。

单链表头删

        链表头删,首先我们要判断链表是否为空,如果为空(空链表没有数据该如何删除呢?

链表头删,我们需要修改plist(*pphead)指向链表的下一个节点即头节点的next指针。

        此外:因为我们的节点都是动态申请的内存,所以在删除时,需要把它释放掉。

思路很简单,写一下代码:
 

//单链表头删
void SLTPopFront(SLT** pphead)
{
	assert(pphead && *pphead);
	SLT* del = (*pphead);
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}

再来看一个如果链表为空,又是啥结果呢?

现在链表已经为空,在执行一次头删代码

这里assert断言报错。

单链表尾删

        首先尾删与头删一样,链表不能为空。

        尾删与尾插也有共同之处,就是遍历链表,找到链表的尾节点。找到尾节点,删除尾节点;然后修改尾节点上一个节点的next指针为NULL;所以在遍历链表时就要多记录一个节点。

//单链表尾删
void SLTPopBack(SLT** pphead)
{
	assert(pphead && *pphead);
	SLT* ptail = *pphead;
	SLT* pcur = *pphead;
	//遍历链表
	while (ptail->next)
	{
		pcur = ptail;
		ptail = ptail->next;
	}
	pcur->next = NULL;
	free(ptail);
	ptail  = NULL;
}

        在测试的时候我们会发现一个问题,如果链表只有一个节点,删除之后我们的plist指针并没有置为空,而我们的空间已经释放掉了,这是很危险的;所以这里我们先判断一下链表是否只有一个节点,如果是,我们释放完空间以后,将(*pphead)置为空,以免出现野指针的情况。

完善后代码:

//单链表尾删
void SLTPopBack(SLT** pphead)
{
	assert(pphead && *pphead);
	if ((*pphead)->next== NULL)
	{
		free(*pphead);
		*pphead = NULL;

	}
	else {
		SLT* ptail = *pphead;
		SLT* pcur = *pphead;
		//遍历链表
		while (ptail->next)
		{
			pcur = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		pcur->next = NULL;
	}
}

测试没有问题,代码能够完成尾删这个功能。

查找数据

        查找数据,遍历链表查找数据,如果找到就返回数据所在节点的地址,如果没有找到就返回NULL;

//查找数据
SLT* SLTFind(SLT* phead, SLType x)
{
	SLT* ptail = phead;
	while (ptail)
	{
		if (ptail->data == x)
		{
			return ptail;
		}
        ptail = ptail->next;
	}
	return NULL;
}

这里测试以下:

数据存在时

数据不存在时

指定节点之前插入

        在链表指定节点之前插入数据,我们还需要知道指定节点的前一个节点,所以仍然需要遍历链表,寻找指定节点pos前一个节点。

//指定位置之前插入
void SLTInsert(SLT** pphead, SLT* pos, SLType x)
{
	assert(pphead && *pphead);
	SLT* ptail = *pphead;
	if (ptail == pos)//头插
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLT* newnode = SLTCreat(x);
		while (ptail->next != pos)//找到pos位置
		{
			ptail = ptail->next;
		}
		newnode->next = pos;
		ptail->next = newnode;
	}
}

当然,这里如果故意传NULL给pos,(这就没啥意义了)这里也可以写以下assert断言pos。

指定节点之后插入

        在指定节点之后插入数据,就不需要再进行遍历链表,这里直接插入即可。

//指定位置之后插入
void SLTInsertAfter(SLT* pos, SLType x)
{
	assert(pos);
	SLT* newnode = SLTCreat(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

删除指定节点

        删除链表中的指定节点,我们需要这个节点的上一个节点,所以又需要遍历链表,找到pos上一个节点,修改pos->next指针指向。

代码如下:

//删除指定节点
void SLTErase(SLT** pphead, SLT* pos)
{
	//找到pos上一个节点
	SLT* ptail = *pphead;
	while (ptail->next != pos)
	{
		ptail = ptail->next;
	}
	SLT* p = pos->next;
	free(pos);
	pos = NULL;
	ptail->next = p;
}

删除指定节点后一个节点

        删除链表指定节点后一个节点,因为pos就是删除节点的上一个节点,所以不需要遍历链表,直接删除即可。

//删除指定位置后一个节点
void SLTEraseAfter(SLT* pos)
{
	assert(pos->next);
	SLT* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

这里如果传过来的就是链表的尾节点,那没办法删除后一个节点,所以断言pos->next;

代码总览

#include"SList.h"
//创建节点
SLT* SLTCreat(SLType x)
{
	SLT* newnode = (SLT*)malloc(sizeof(SLT));
	assert(newnode);
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//输出链表
void SLTPrint(SLT* phead)
{
	SLT* ptail = phead;
	while (ptail != NULL)//也可以直接写成 ptail
	{
		printf("%d -> ", ptail->data);
		ptail = ptail->next;
	}
	printf("NULL\n");
}
//单链表头插
void SLTPushFront(SLT** pphead, SLType x)
{
	assert(pphead);
	SLT* newnode = SLTCreat(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
//单链表尾插
void SLTPushBack(SLT** pphead, SLType x)
{	
	assert(pphead);
	SLT* newnode = SLTCreat(x);
	if (*pphead == NULL) //判断链表是否为空
	{
		*pphead = newnode;
	}
	else {
		SLT* ptail = *pphead;
		//遍历链表
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}
//单链表头删
void SLTPopFront(SLT** pphead)
{
	assert(pphead && *pphead);
	SLT* del = (*pphead);
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}
//单链表尾删
void SLTPopBack(SLT** pphead)
{
	assert(pphead && *pphead);
	if ((*pphead)->next== NULL)
	{
		free(*pphead);
		*pphead = NULL;

	}
	else {
		SLT* ptail = *pphead;
		SLT* pcur = *pphead;
		//遍历链表
		while (ptail->next)
		{
			pcur = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		pcur->next = NULL;
	}
}
//查找数据
SLT* SLTFind(SLT* phead, SLType x)
{
	SLT* ptail = phead;
	while (ptail)
	{
		if (ptail->data == x)
		{
			return ptail;
		}
		ptail = ptail->next;
	}
	return NULL;
}
//指定位置之前插入
void SLTInsert(SLT** pphead, SLT* pos, SLType x)
{
	assert(pphead && *pphead);
	SLT* ptail = *pphead;
	if (ptail == pos)//头插
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLT* newnode = SLTCreat(x);
		while (ptail->next != pos)//找到pos位置
		{
			ptail = ptail->next;
		}
		newnode->next = pos;
		ptail->next = newnode;
	}
}
//指定位置之后插入
void SLTInsertAfter(SLT* pos, SLType x)
{
	assert(pos);
	SLT* newnode = SLTCreat(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//删除指定节点
void SLTErase(SLT** pphead, SLT* pos)
{
	//找到pos上一个节点
	SLT* ptail = *pphead;
	while (ptail->next != pos)
	{
		ptail = ptail->next;
	}
	SLT* p = pos->next;
	free(pos);
	pos = NULL;
	ptail->next = p;
}
//删除指定位置后一个节点
void SLTEraseAfter(SLT* pos)
{
	assert(pos->next);
	SLT* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

感谢各位大佬支持并指出问题,

如果本篇内容对你有帮助,可以一键三连支持以下,感谢支持!!!

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

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

相关文章

《梦醒蝶飞:释放Excel函数与公式的力量》8.8 STDEVP函数

8.8 STDEVP函数 STDEVP函数是Excel中用于计算总体数据的标准偏差的函数。标准偏差是统计学中的一个重要指标,用于衡量数据集中各数值偏离平均值的程度。总体标准偏差考虑了整个数据集,而不是样本。 8.8.1 函数简介 STDEVP函数用于返回总体数据的标准偏…

Java实现日志全链路追踪.精确到一次请求的全部流程

广大程序员在排除线上问题时,会经常遇见各种BUG.处理这些BUG的时候日志就格外的重要.只有完善的日志才能快速有效的定位问题.为了提高BUG处理效率.我决定在日志上面优化.实现每次请求有统一的id.通过id能获取当前接口的全链路流程走向. 实现效果如下: 一次查询即可找到所有关…

前端面试题13(API请求方法)

在前端JavaScript中,进行API请求主要可以通过几种方式来实现,最常见的是使用XMLHttpRequest(较旧的方法)、fetch(现代浏览器推荐方法)以及使用第三方库如axios或jQuery.ajax等。 1. XMLHttpRequest 这是最…

MPI hello world SSH 免密互联

目标: 我们想实现2台主机免密互联,将MPI Hello World跑起来 假设hostname是node01,node02,(Linux shell窗口一般是UserNameHostName,node1和node2一定要和HostName一样) hostname是/etc/hosts中的配置,如下…

工地/矿区/电力/工厂/环卫视频智能安全监控反光衣AI检测算法的原理及场景应用

一、引言 随着科技的快速发展,特别是在智能交通和安全生产领域,对于夜间或弱光环境下的人员识别和安全监控需求日益凸显。反光衣作为一种重要的安全装备,被广泛应用于道路施工、工地作业、夜间巡逻、安全生产等场景,旨在提高人员的…

【CUDA】 矩阵乘法 matMatMul

矩阵乘法 matMatMul 矩阵乘法是基本线性代数子程序(BLAS)的重要组成部分,而且线性代数中许多其他操作以此为基础。 图1是两个矩阵的乘法。 基础方法,正方形tile和长方形tile 基础方法 执行矩阵乘法的基础方法是使用单个线程执…

2021-06-15 protues(ISIS)脉冲发生器仿真仪表使用

缘由这个脉冲发生器怎么连线_编程语言-CSDN问答

STM32智能交通管理系统教程

目录 引言环境准备智能交通管理系统基础代码实现:实现智能交通管理系统 4.1 数据采集模块 4.2 数据处理与分析 4.3 控制系统实现 4.4 用户界面与数据可视化应用场景:交通管理与优化问题解决方案与优化收尾与总结 1. 引言 智能交通管理系统利用STM32嵌…

【对顶堆 优先队列】295. 数据流的中位数

本文涉及知识点 对顶堆 优先队列 LeetCode295. 数据流的中位数 中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。 例如 arr [2,3,4] 的中位数是 3 。 例如 arr [2,3] 的中位数是 (2 3) / 2 2.5 …

网络漏洞挖掘实测报告

关于作者:个人主页 网络漏洞挖掘实测报告 一、前言 网络漏洞挖掘是信息安全领域中至关重要的一环。通过挖掘和修复漏洞,可以有效地保护系统免受潜在的攻击和破坏。本报告旨在记录一次完整的网络漏洞挖掘实测过程,包括实施方法、过程、结果以…

免杀笔记 ----> ShellCode Loader !!!

学了那么久的前置知识,终于到了能上线的地方了!!! 不过这里还没到免杀的部分,距离bypass一众的杀毒软件还有很长的路要走!! 目录 1.ShellCode 2.ShellCode Loader的概念 3.可读可写可…

Android实现获取本机手机号码

和上次获取设备序列号一样,仍然是通过无障碍服务实现,在之前的代码基础上做了更新。代码和demo如下: package com.zwxuf.lib.devicehelper;import android.accessibilityservice.AccessibilityService; import android.app.Activity; import…

Java中使用arima预测未来数据

看着已经存在的曲线图数据,想预估下后面曲线图的数据。 import java.util.Vector;public class AR {double[] stdoriginalData{};int p;ARMAMath armamathnew ARMAMath();/*** AR模型* param stdoriginalData* param p //p为MA模型阶数*/public AR(double [] stdori…

Dungeonborne延迟高?降低Dungeonborne延迟的方法分享

Dungeonborne是Mithril Interactive开发并发行的一款沉浸式第一人称 PvPvE 地下城探险游戏。Dungeonborne的魅力并不仅仅在于战斗和冒险。游戏中的剧情设计同样引人入胜,每个NPC都有自己独特的故事和背景,玩家在与他们交流的过程中,不仅能了解…

Tomcat(+Servlet)笔记+代码

Tomcat安装和配置 安装在不含中文的路径,路径不能太长 Apache 官网👇 Apache Tomcat - Welcome! 配置部分 点击下图红框处,找到Tomcat安装位置 添加项目的文件 配好的话,红框这里有个猫 代码部分 新建jsp文件,里…

【码银送书第二十二期】《Python数据分析从入门到精通(第2版)》

💐大家好!我是码银~,欢迎关注💐: CSDN:码银 公众号:码银学编程 前言 🍀丛书说明:“软件开发视频大讲堂‘’丛书第1版于2008年8月出版,因其编写细腻、易学实用…

字符串函数5-9题(30 天 Pandas 挑战)

字符串函数 1. 相关知识点1.5 字符串的长度条件判断1.6 apply映射操作1.7 python大小写转换1.8 正则表达式匹配2.9 包含字符串查询 2. 题目2.5 无效的推文2.6 计算特殊奖金2.7 修复表中的名字2.8 查找拥有有效邮箱的用户2.9 患某种疾病的患者 1. 相关知识点 1.5 字符串的长度条…

Orangepi配合IIC驱动OLED屏幕

目录 一、OLED屏幕 二、Orangepi的IIC接口及OLED屏幕硬件接线 2.1 Orangepi的IIC接口: 2.2 Orangepi与OLED屏幕硬件接线: 三、wiringPi库示例代码 3.1 wiringPi库OLED屏幕示例代码: 3.2 OLED显示自己想要的字符: 一、OLED屏…

E2.【C语言】练习:static部分

#include <stdio.h> int sum(int a) {int c 0;static int b 3;c 1;b 2;return (a b c); } int main() {int i;int a 2;for (i 0; i < 5;i){printf("%d ", sum(a));} } 求执行结果 c是auto类变量(普通的局部变量)&#xff0c;自动产生&#xff0c…

第26篇 寻找最大数<一>

Q&#xff1a;如何设计一段汇编语言子程序并调用来寻找一组数中的最大数呢&#xff1f; A&#xff1a;基本原理&#xff1a;可以使用子程序LARGE实现找到列表中最大数的功能。主程序通过寄存器将列表的条目数和起始地址作为参数传递给子程序&#xff0c;子程序通过寄存器将最大…