数据结构入门指南:单链表(附源码)

news2024/11/8 23:53:18

目录

前言

尾删

头删

查找

位置前插入

 位置后插入

 位置删除

 位置后删除

 链表销毁

总结


前言

        前边关于链表的基础如果已经理解透彻,那么接下来就是对链表各功能的实现,同时也希望大家能把这部分内容熟练于心,这部分内容对有关链表部分的刷题很有帮助。废话不多讲我们步入正题。


        前边已经实现了头插、尾插的操作,今天主要的内容是:头删、尾删、位置删除、位置后删除、查找、位置前插入、位置后插入。

尾删

        要想进行尾删,就要先找到链表的尾。

        我们知道单链表的缺点之一就是只可以单向遍历,所以要想删除最后一个节点,就要先找到倒数第二个节点,然后释放掉最后一个节点,将倒数第二个节点的next(指针域)置为NULL。

 

 具体代码实现:

void SLPopBlack(SLNode** pphead)
{
	
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	SLNode* tail = *pphead;

	while (tail->next->next)
	{
		tail = tail->next;
	}
	free(tail->next);
	tail->next = NULL;
}

        这里为什么要传二级指针?有人可能会有这样的疑惑, 传二级指针就是为了防止链表被删空的情况,在链表的许多情节中都要考虑像链表删空等这种极端情况,要做到面面俱到。

        当然我们也可以选择不使用二级指针,而是直接返回头指针的地址。但这样函数的类型就变成了结构体指针类型,而要调用这个函数还需要相同类型的结构体指针变量接收,这种情况在刷题中经常遇到,但在写链表时不推荐,这样写调用函数会比较麻烦。

头删

头删大家可以先思考一下,需不需要使用二级指针。

有这样一个链表:

         想要删除第一个节点,只需要把头指针指向的位置改为指向第二个节点。把头指针修改,这是修改结构体指针,到这里想必大家已经清楚,需要使用二级指针。

        接下来我们理一下删除的逻辑,直接将头指针指向第二个节点,这样就会造成第一个节点丢失没办法释放掉空间。如果先将第一个节点释放就会使第二个节点丢失,头指针无法连接剩余节点。

        这要怎么解决呢?这里就需要创建一个新的变量来存储一下第二个节点的地址,然后再将第一个节点释放。 

具体代码实现:

void SLPopFront(SLNode** pphead)
{
	assert(*pphead);

	SLNode* newhead = (*pphead)->next;
	free(*pphead);
	*pphead = newhead;
}

查找

        查找很简单,顺序表的查找返回的是下标,而链表返回的是节点的地址,后续的操作也是比较简单,我就不再画逻辑图。

SLNode* SLFind(SLNode* phead, Datatype x)
{
	SLNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

位置前插入

        位置前插入,如果是在第一个节点位置前插入,就是头插,其次是要想在位置前插入就必须要知道前有个节点,单链表是无法逆向遍历的,所以要想知道前一个节点就必须要传头指针。然后将前一个节点的next置为新节点的地址,新节点的next置为pos位置节点的地址。

void SLFrontInsert(SLNode** pphead, SLNode* pos, Datatype x)
{
	
	assert(pos);
	if (pos == *pphead)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLNode* prev = *pphead;
		SLNode* newnode = NewNode(x);
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}
}

 位置后插入

        位置后插入,可以不需要头指针。操作也非常简单,把pos位置的下一个节点赋给新节点的next,把新节点的地址赋给pos位置节点的next。这里有人可能会有疑惑,不考虑极端情况吗?位置后插入是无法进行头插的,如果链表为空,传进来pos就为空,就会直接保错,至于尾插,这段代码也是可以解决的。

void SLAfterInsert(SLNode* pos, Datatype x)
{
	assert(pos);
	SLNode* newnode = NewNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
	
}

 位置删除

        位置删除,需要把pos位置前一个节点的next置为pos位置下一个节点的地址,同时还需要将删除的节点释放空间。考虑极端情况,如果删除位置是第一个节点,这种方法就失效了,因为没有第一个节点的前一个节点,这时也就是头删,我们可以调用前边已经实现的函数接口。

void SLErase(SLNode** pphead, SLNode* pos) {
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

 位置后删除

        位置后删除,如果位置为最后一个节点,就不需要删除,且位置后删除无法进行头删,然后是正常情况,把pos位置节点的next置为pos位置后第二个节点的地址,就完成了。那是否可以这样写呢?

pos->next = pos->next->next

 答案是不可以,这样会造成pos后一个节点丢失,无法释放。所以这里我们需要分成两步来写:

void SLEraseAfter(SLNode* pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		return;
	}
	else
	{
		SLNode* posnext = pos->next;//不可以写成一步,否则pos后一个节点就会丢失,无法释放
		pos->next = posnext->next;
		free(posnext);
		posnext = NULL;
	}

}

 链表销毁

        执行完所有操作后,就需要将链表销毁了

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

 完整代码:

SList.c

#include"SList.h"
void SLprint(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
SLNode* NewNode(Datatype x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void SLPushBlack(SLNode** pphead, Datatype x)
{
	assert(pphead);
	SLNode* newnode = NewNode(x);
	SLNode* tail = *pphead;
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
void SLPushFront(SLNode** pphead, Datatype x)
{
	assert(pphead);
	SLNode* newnode = NewNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
void SLPopBlack(SLNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	SLNode* tail = *pphead;

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


	SLNode* newhead = (*pphead)->next;
	free(*pphead);
	*pphead = newhead;
}
SLNode* SLFind(SLNode* phead, Datatype x)
{
	SLNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
void SLFrontInsert(SLNode** pphead, SLNode* pos, Datatype x)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLNode* prev = *pphead;
		SLNode* newnode = NewNode(x);
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}
}
void SLAfterInsert(SLNode* pos, Datatype x)
{
	assert(pos);
	SLNode* newnode = NewNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
	
}
void SLErase(SLNode** pphead, SLNode* pos) {
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
void SLEraseAfter(SLNode* pos)
{
	assert(pos);
	if (pos->next == NULL)
	{
		return;
	}
	else
	{
		SLNode* posnext = pos->next;
		pos->next = posnext->next;
		free(posnext);
		posnext = NULL;
	}

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

 SList.h

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int Datatype;
typedef struct SLNode
{
	Datatype data;
	struct SLNode* next;
}SLNode;
//打印链表
void SLprint(SLNode* phead);
//创建新节点
SLNode* NewNode(Datatype x);
//尾插
void SLPushBlack(SLNode** phead, Datatype x);
//头插
void SLPushFront(SLNode** pphead, Datatype x);

//尾删
void SLPopBlack(SLNode** pphead);
//头删
void SLPopFront(SLNode** pphead);
//查找
SLNode* SLFind(SLNode* phead, Datatype x);
//pos位置前插入
void SLFrontInsert(SLNode** pphead, SLNode* pos, Datatype x);
//pos位置后插入
void SLAfterInsert(SLNode* pos, Datatype x);
//pos位置后删除
void SLEraseAfter(SLNode* pos);
//pos位置删除
void SLErase(SLNode** pphead, SLNode* pos);

void SLDestory(SLNode** pphead);

 test.c

这里基本都是测试接口,没有什么太大的参考价值,代码如下,便于大家调试。

#include"SLNode.h"
void test1()
{
	SLNode* plist = NULL;
	int n = 0;
	printf("请输入链表的长度\n");
	scanf("%d", &n);
	printf("请输入数据\n");
	for (int i = 0; i < n; i++)
	{
		int val = 0;
		scanf("%d", &val);
		SLNode *newnode= NewNode(val);
		newnode->next = plist;
		plist = newnode;
	}
	SLNode* pos= SLFind(plist, 2);
	if (pos)
	{
		pos->data *= 10;
	}
	SLFrontInsert(&plist, pos, 10);
	SLprint(plist);

	SLPushBlack(&plist,100);
	SLprint(plist);

	SLPushFront(&plist, 200);
	SLprint(plist);

	SLPopBlack(&plist);
	SLprint(plist);

	SLPopFront(&plist);
	SLprint(plist);
}
void test2()
{
	SLNode* plist = NULL;
	SLPushBlack(&plist, 1);
	SLPushBlack(&plist, 2);
	SLPushBlack(&plist, 3);
	SLPushBlack(&plist, 4);
	SLPushBlack(&plist, 5);
	SLprint(plist);
	SLNode* pos = SLFind(plist, 5);
	SLAfterInsert(pos, 20);
	SLprint(plist);
	SLFrontInsert(&plist, pos, 10);
	SLprint(plist);
}
void test3()
{
	SLNode* plist = NULL;
	SLPushBlack(&plist, 1);
	SLPushBlack(&plist, 2);
	SLPushBlack(&plist, 3);
	SLPushBlack(&plist, 4);
	SLPushBlack(&plist, 5);
	SLprint(plist);
	SLNode* pos = SLFind(plist, 1);
	//SLErase(&plist, pos);
	SLEraseAfter(pos);
	SLprint(plist);
	SLDestory(&plist);
	
}

int main()
{
	test2();
	

	return 0;
}

总结

        好的,内容到这里就要结束了,这部分内容或许看来很繁琐,但在刷链表相关的题时就会惊奇的发现,题解都是这些操作的变形。熟练这部分内容,可以让你在刷链表相关的题时会感觉非常的爽,刷题也会更加顺利。最后,感谢阅读!

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

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

相关文章

CustomeG6-canvas

目录 简介 scss 快速上手 语雀 简介 antv/g6是一款基于JavaScript的图形可视化引擎&#xff0c;由阿里巴巴的AntV团队开发。 创建各种类型的图形&#xff0c;如流程图、关系图、树形图等。 G6采用了自己的绘图模型和渲染引擎&#xff0c;使其具备高性能的图形渲染能力。…

npm更新和管理已发布的包

目录 1、更改包的可见性 1.1 将公共包设为私有 ​编辑 使用网站 使用命令行 1.2 将私有包公开 使用网站 使用命令行 2、将协作者添加到用户帐户拥有的私有包 2.1 授予对Web上私有用户包的访问权限 2.2 从命令行界面授予私有包访问权限 2.3 授予对私有组织包的访问权限…

InfiniBand,到底是个啥?

对于InfiniBand&#xff0c;很多搞数通的同学肯定不会陌生。 进入21世纪以来&#xff0c;随着云计算、大数据的不断普及&#xff0c;数据中心获得了高速发展。而InfiniBand&#xff0c;就是数据中心里的一项关键技术&#xff0c;地位极为重要。 尤其是今年以来&#xff0c;以Ch…

春秋云镜 CVE-2021-32682

春秋云镜 CVE-2021-32682 elFinder RCE 靶标介绍 elFinder是一套基于Drupal平台的、开源的AJAX文件管理器。该产品提供多文件上传、图像缩放等功能;elFinder 存在安全漏洞&#xff0c;攻击者可利用该漏洞在托管elFinder PHP连接器的服务器上执行任意代码和命令。 启动场景 漏…

金蝶管易云 X Hologres:新一代全渠道电商ERP最佳实践

业务简介 金蝶管易云是金蝶集团旗下专注提供电商企业管理软件服务的子公司&#xff0c;成立于2008年&#xff0c;是国内最早的电商ERP服务商之一&#xff0c;目前已与300主流电商平台建有合作关系&#xff0c;以企业数据为驱动&#xff0c;深度融合线上线下数据&#xff0c;为…

pytorch学习——正则化技术——丢弃法(dropout)

一、概念介绍 在多层感知机&#xff08;MLP&#xff09;中&#xff0c;丢弃法&#xff08;Dropout&#xff09;是一种常用的正则化技术&#xff0c;旨在防止过拟合。&#xff08;效果一般比前面的权重衰退好&#xff09; 在丢弃法中&#xff0c;随机选择一部分神经元并将其输出…

HCIP中期实验

1、该拓扑为公司网络&#xff0c;其中包括公司总部、公司分部以及公司骨干网&#xff0c;不包含运营商公网部分。 2、设备名称均使用拓扑上名称改名&#xff0c;并且区分大小写。 3、整张拓扑均使用私网地址进行配置。 4、整张网络中&#xff0c;运行OSPF协议或者BGP协议的设备…

Hadoop 之 Hive 4.0.0-alpha-2 搭建(八)

Hadoop 之 Hive 搭建与使用 一.Hive 简介二.Hive 搭建1.下载2.安装1.解压并配置 HIVE2.修改 hive-site.xml3.修改 hadoop 的 core-site.xml4.启动 三.Hive 测试 一.Hive 简介 Hive 是基于 Hadoop 的数据仓库工具&#xff0c;可以提供类 SQL 查询能力 二.Hive 搭建 1.下载 H…

linux 安装FTP

检查是否已经安装 $] rpm -qa |grep vsftpd vsftpd-3.0.2-29.el7_9.x86_64出现 vsftpd 信息表示已经安装&#xff0c;无需再次安装 yum安装 $] yum -y install vsftpd此命令需要root执行或有sudo权限的账号执行 /etc/vsftpd 目录 ftpusers # 禁用账号列表 user_list # 账号列…

【Ajax】笔记-设置CORS响应头实现跨域

CORS CORS CORS是什么&#xff1f; CORS(Cross-Origin Resource Sharing),跨域资源共享。CORS是官方的跨域解决方案&#xff0c;它的特点是不需要在客户端做任何特殊的操作&#xff0c;完全在服务器中进行处理&#xff0c;支持get和post请求。跨域资源共享标准新增了一组HTTP首…

VSCode格式化shell脚本

安装格式化插件&#xff1a;shell-format 用VSCode打开shell脚本之后&#xff0c;按格式化快捷键CtrlAltF&#xff0c;会提示没有格式化shell的工具&#xff0c;然后安装插件&#xff0c;我装的是这个插件&#xff1a;shell-format。 介绍&#xff1a;https://marketplace.vis…

2.C语言数据类型

常量与变量 1.**常量&#xff1a;**程序运行中&#xff0c;值不改变的量 变量&#xff1a;int num5&#xff1b;值可以变的量 2 C语言三种简单数据类型&#xff1a;整型&#xff0c;实型&#xff0c;字符型 %c %d %ld %s %f整型-进制的转换 **1.十进制&#xff1a;**默认的进…

SpringBoot使用JKS或PKCS12证书实现https

SpringBoot使用JKS或PKCS12证书实现https 生成JKS类型的证书 可以利用jdk自带的keytool工具来生成证书文件&#xff0c; 默认生成的是JKS证书 cmd命令如下: 执行如下命令&#xff0c;并按提示填写证书内容&#xff0c;最后会生成server.keystore文件 keytool -genkey tomcat…

ChatGPT在商业世界中的创新应用:颠覆传统营销与客户关系管理

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

14、容器初始化器和配置环境后处理器

容器初始化器和配置环境后处理器 容器初始化器 与传统Spring的容器后处理器&#xff08;也可对容器做一些定制&#xff09;对比&#xff0c;此处的容器初始化器的执行时机要更早一些。 容器初始化器负责可对Spring容器执行初始化定制。 就是在启动项目的时候&#xff0c;在容…

eclipse版本与jdk版本对应关系

官网&#xff1a;Eclipse/Installation - Eclipsepedia eclipse历史版本&#xff08;2007-&#xff09;&#xff1a;Older Versions Of Eclipse - Eclipsepedia Eclipse Packaging Project (EPP) Releases | Eclipse Packages

springboot疾病查询网站【纯干货分享,免费领源码01548】

spring boot疾病查询网站 摘 要 随着互联网时代的到来&#xff0c;同时计算机网络技术高速发展&#xff0c;网络管理运用也变得越来越广泛。因此&#xff0c;建立一个B/S结构的疾病查询网站&#xff0c;会使疾病查询工作系统化、规范化&#xff0c;也会提高医院形象&#xff0c…

红外雨量计(光学雨量传感器)检测降雨量,预防内涝

红外雨量计&#xff08;光学雨量传感器&#xff09;检测降雨量&#xff0c;预防内涝 随着城市化进程的加快&#xff0c;城市内涝成为一个愈发严峻的问题。短时间内大量的降雨&#xff0c;不仅会给城市交通带来困难&#xff0c;也会对城市的基础设施和居民的生活造成很大的影响…

ESP32cam系列教程003:ESP32cam实现远程 HTTP_OTA 自动升级

文章目录 1.什么是 OTA2. ESP32cam HTTP_OTA 本地准备2.1 HTTP OTA 升级原理2.2 开发板本地基准程序&#xff08;程序版本&#xff1a;1_0_0&#xff09;2.3 开发板升级程序&#xff08;程序版本&#xff1a;1_0_1&#xff09;2.4 本地 HTTP_OTA 升级测试2.4.1 本地运行一个 HT…

Spring系列二:基于注解配置bean

文章目录 &#x1f497;通过注解配置bean&#x1f35d;基本介绍&#x1f35d;快速入门&#x1f35d;注意事项和细节 &#x1f497;自己实现Spring注解配置Bean机制&#x1f35d;需求说明&#x1f35d;思路分析&#x1f35d;注意事项和细节 &#x1f497;自动装配 Autowired&…