C语言数据结构基础-单链表

news2025/2/25 12:39:03

1.链表概念

       在前面的学习中,我们知道了线性表,其中逻辑结构与物理结构都连续的叫顺序表,那么:

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

2.链表组成

单链表元素(节点)由保存的数据和下个单元的地址组成

 

//结点结构体的定义
struct SListNode{
  int data;
  struct SListNode* next;
};

(node就是结点的意思)

国际惯例,我们进行typdef

typedef struct SListNode{
  int data;
  struct SListNode* next;
}SLTNode;

那么此时我们可不可以在其中定义next的时候也用SLTNode呢?

SLTNode* next;

       当然是不可以的,此时使用的SLTNode还未被定义,编译器还未能识别,使用了就会报错

为什么要用malloc开辟空间?

为了之后删除(使用free函数释放指针指向的空间)的时候方便,进行初始化时用malloc给每一个节点开辟空间

3.单链表各功能的实现

为了便于链表结点的创建和初始化,我们将其初始功能进行封装

SLTNode* SLTBuyNode(SLTDataType x) {
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL) {
		perror("malloc fail!");
		exit(1);
	}
	newnode->next = NULL;
	newnode->data = x;
	return newnode;
}
 1.尾差

基于顺序表的经验,大概分为两种情况,一种是链表为空,另一种是链表不为空

我们首先传入该链表的头节点,通过直接链接或者先遍历再链接。

传一个指针进函数,对指针进行操作(每一个结点都有元素与其指针等级),那么以上函数就算传值调用,并不能让phead真正等于newnode 

此处的传指针就是传值调用,指针作为变量来修改,就应该传指针的地址,才能达到传址调用的目的。

传递一个指针,可以在函数内部直接修改该指针指向空间的内容,但是想修改、操作传递来的指针,就必须传该指针的指针,才能通过该指针的指针来修改指针的内容。 

       先让创建的新结构体的next找到现在的phead(也就是*pphead,因为函数中不再存在phead这个变量)

再把现在处于第一个的结构体(我们刚刚自己创建的newnode)的地址给到phead

形参是实参的拷贝。

void SLTPushBack(SLTNode** pphead, SLTDataType x) {
	assert(pphead);

	SLTNode* newnode = SLTBuyNode(x);

	//链表为空,新节点作为phead
	if (*pphead == NULL) {
		*pphead = newnode;
		return;
	}
	//链表不为空,找尾节点
	SLTNode* ptail = *pphead;
	while (ptail->next)
	{
		ptail = ptail->next;
	}
	//ptail就是尾节点
	ptail->next = newnode;
}

为了便于主函数的调用和检验,我们写出能打印其数据的函数

void SLTPrint(SLTNode* phead) {
	assert(phead);
	SLTNode* pos = phead;
	while (pos) {
		printf("%d->",pos->data);
		pos = pos->next;
	}
	printf("NULL");
}

这样就能检验我们的尾插功能了。 

2.头插

此处同理,我们任然需要传一个二级指针,通过二级指针对链表进行修改。

依然是分两种情况,实现如下:

void SLTPushFront(SLTNode** pphead, SLTDataType x) {
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
    //若为空
	if (*pphead == NULL) {
		*pphead = newnode;
		return;
	}
	//链接newnode 和 *pphead
	newnode->next = *pphead;
	*pphead = newnode;
}

能否调换以下两句的顺序? 

当然不行,否则新节点将自己的地址赋给了自己的next。

由此可见,链表的实现,重在理清个节点之间的链接顺序。

3.尾删

释放空间,让原本倒数第二个结点的next指向NULL(先free,再置NULL)

多个节点时,我们需要先遍历链表找到最后一个节点的前驱节点ptail(原来的倒数第二个节点),删除最后一个节点的同时改变前驱节点的next的值

但如果只有一个节点呢?

if ((*pphead)->next == NULL) {
	free(*pphead);
	*pphead = NULL;
	return;
}

    所以直接先free再置NULL即可 ,但请注意->和*的优先级不同,箭头是最高优先级,所以需要使用括号

void SLTPopBack(SLTNode** pphead) {
	assert(pphead);
	//没有节点时
	assert(*pphead);
	//只有一个节点时
	if ((*pphead)->next) {
		free(*pphead);
		*pphead = NULL;
		return;
	}
	//多个节点时
	SLTNode* ptail = *pphead;
	while (ptail->next->next) {//很明显,只有一个节点的时候过不了,那么我们就需要在多个节点之前写出只有一个节点的情况
		ptail = ptail->next;
	}
	free(ptail->next);
	ptail->next = NULL;
}

尽管看上去是先写的一个节点,再写的多个节点,但正常逻辑应该是先写多个(通常情况)再考虑一个(特殊情况)

tips:

遍历链表与找链表尾结点是不一样的

(补充一下遍历链表的方法) 

SLTNode* ptail = *pphead;
SLTNode* prev = NULL;
while (ptail->next)
{
	prev = ptail;
	ptail = ptail->next;
}
4.头删

如果链表已经为空的话应该直接退出,所以依然分三种情况考虑

void SLTPopFront(SLTNode** pphead) {
	assert(pphead);
	//没有节点 一个节点 多个节点
	assert(*pphead);

	SLTNode* tmp = *pphead;
	*pphead = (*pphead)->next;
	free(tmp);
	tmp = NULL;
	//发现此时一个节点和多个节点都能通过这段代码
}

依然是处理pphead和pphead->next的关系,此处又有一个小技巧,如过发现不能很好的直接通过等式调整各个指针所指向的位置,可以通过建立新的变量拷贝需要处理的数据。 

5.指定位置前后的插入

为了能找到指定的位置,我们先写出一个查找函数

SLTNode* SLTFind(SLTNode* phead, SLTDataType x) {
	assert(phead);
	while (phead->next) {
		if (phead->data == x) {
			return phead;
		}
		phead = phead->next;
	}
	//没有找到
	return NULL;
}

 为什么要断言链表不为空?因为如果链表为空,传进来的pos也必须为空,矛盾。

pos应当是已知链表(首节点地址为*pphead的)一个具体特定结点,而非NULL

当然,此处也可以不传二级指针只传一级指针,因为我们也可以不需要对传入的指针进行操作,但是为了接口的一致性,也可以都使用二级指针接口

void SLTInsertAfter(SLTNode* pos, SLTDataType x) {
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next=newnode;
}

void SLTInsertBefore(SLTNode* phead, SLTNode* pos, SLTDataType x) {
	assert(pos);
	assert(phead);
	
	if (pos == phead) {
		SLTPushFront(&phead, x);
		return;
	}

	SLTNode* newnode = SLTBuyNode(x);
	SLTNode* prev = phead;
	while (prev->next != pos) {
		prev = prev->next;
	}
	newnode->next = pos;
	prev->next = newnode;
	
}

为什么在指定位置前插入数据需要头结点的位置,而指定位置后的不需要呢(指定位置之前插入需要三个参数,指定位置之后插入需要两个参数)?

因为根据单链表的特性,除了最后一个节点,所有节点都能通过自己找到下一个节点,但是找不到上一个节点,所以当我们需要再指定位置之前插入数据时,就需要遍历链表来找到指定位置。

6.删除指定位置之后的节点

 此处的条件为pos->next不为空,具体操作就是链接pos,pos->next,pos->next->next之间的关系

void SLTEraseAfter(SLTNode* pos) {
	assert(pos);
	if (pos->next == NULL) {
		return;
	}
	//pos pos->next pos->next->next
	SLTNode* tmp = pos->next;
	pos->next = pos->next->next;
	free(pos->next);
	pos->next == NULL;
}
7.删除指定位置的节点

同理

void SLTErase(SLTNode* phead, SLTNode* pos) {
	assert(phead);
	assert(pos);
	//刚好是头则执行头删
	if (phead == pos) {
		SLTPopFront(&phead);
		return;
	}
	while (phead->next != pos) {
		phead = phead->next;
	}
	phead->next = pos->next;
	free(pos);
	pos = NULL;
}

6,7检测如下 

4.链表分类

上文的单链表Slist就是singel linked list(单向不带头不循环链表)

依据不同的特点,共有八种链表

前文提到的头结点与“带头”的头不一样,前者为第一个有效节点,后者是所谓的哨兵位,不存储有效数据 

       虽然链表种类非常多,但是最实用的只有:单链表、双向链表(带头双向循环链表)

本篇中,已经实现了单链表的大多数功能。

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

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

相关文章

力扣550 游戏玩法分析 IV

目录 题目描述 思路整理 1. 首次登录日期 2. 第二天登录 3. 计算比率 实现思路 完整代码及解释 题目描述 Table: Activity ----------------------- | Column Name | Type | ----------------------- | player_id | int | | device_id | int | | ev…

ICVQUANTUMCHINA报告:《2024全球量子计算产业发展展望》

2月20日,《2024量子计算产业发展展望》的中文版报告通过光子盒官方平台发布,英文版报告通过ICV官方平台发布。 英文版报告获取地址: https://www.icvtank.com/newsinfo/897610.html 在过去的一年里,光子盒与您一同见证了全球量子…

改进的yolo交通标志tt100k数据集目标检测(代码+原理+毕设可用)

YOLO TT100K: 基于YOLO训练的交通标志检测模型 在原始代码基础上: 修改数据加载类,支持CoCo格式(使用cocoapi);修改数据增强;validation增加mAP计算;修改anchor; 注: 实验开启weig…

面试数据库篇(mysql)- 03MYSQL支持的存储引擎有哪些, 有什么区别

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是基于库的,所以存储引擎也可被称为表类型。 MySQL体系结构 连接层服务层引擎层存储层 存储引擎特点 InnoDB MYSQL支持的存储引擎有哪些, 有什么区别 ? my…

C++初阶:模版相关知识的进阶内容(非类型模板参数、类模板的特化、模板的分离编译)

结束了常用容器的介绍,今天继续模版内容的讲解: 文章目录 1.非类型模版参数2.模板的特化2.1模版特化引入和概念2.2函数模版特化2.3类模板特化2.3.1全特化2.3.1偏特化 3. 模板分离编译3.1分离编译概念3.2**模板的分离编译**分析原因 1.非类型模版参数 模板…

vulnhub-----Hackademic靶机

文章目录 1.C段扫描2.端口扫描3.服务扫描4.web分析5.sql注入6.目录扫描7.写马php反弹shell木马 8.反弹shell9.内核提权 1.C段扫描 kali:192.168.9.27 靶机:192.168.9.25 ┌──(root㉿kali)-[~] └─# arp-scan -l Interface: eth0,…

tinymce上传图片或者其他文件等等

技术选型 tips: tinymce在vue中常用的有两种方式 第一种: 官方组件,点我 优点: 不用自己封装组件 缺点: 需要申请特定apikey,类似于百度,高德地图; 第二种: 就是下面这种 优点: 不需要申请特定的apikey 缺点: 需要自己手动的封装组件,灵活性高 Vue 2.x和3.x基本没有区别 tinym…

《Redis 设计与实现》读书概要

注: 《Redis 设计与实现》一书基于 Redis 2.9 版本编写,部分内容已过时,过时之处本文会有所说明。本文为读书笔记,部分简单和日常使用较少的知识点未记录。原书网页版地址 https://redisbook.com/ 一、底层数据结构 SDS(Simple Dy…

dcat admin 自定义页面

自定义用户详情页 整体分为两部分:用户信息、tab框 用户信息采用自定义页面加载,controller代码如下: protected function detail($id) {return Show::make($id, GameUser::with(finance), function (Show $show) {// 这段就是加载自定义页面…

Window部署Jaeger

参考:windows安装使用jaeger链路追踪_windows安装jaeger-CSDN博客 下载:Releases jaegertracing/jaeger GitHub Jaeger – Download Jaeger 目录 1、安装nssm 2、安装运行 elasticsearch 3、安装运行 3.1部署JaegerAgent 3.2部署JaegerCollec…

MySQL 存储过程批量插入总结

功能需求背景:今天接到产品经理核心业务表的数据压测功能,让我向核心业务表插入百万级的业务量数据,我首先想到的办法就是存储过程实现数据的批量 。 由于无法提供核心业务表,本文仅仅提供我刚刚自己创建的表bds_base_user 表做相…

7-AMCA NHS ester,113721-87-2,可以将荧光基团特异性地连接到目标分子上

113721-87-2,7-AMCA NHS ester,AMCA-OSu,AMCA-NHS,AMCA N-succinimidyl ester,7-AMCA NHS 活化酯,7-氨基-4-甲基香豆素-3-乙酸 N-琥珀酰亚胺酯,可以将荧光基团特异性地连接到目标分子上 您好&a…

IDC 中搭建 Serverless 应用平台:通过 ACK One 和 Knative 玩转云资源

作者:元毅、庄宇 如何打造云上(公共云)、云下(IDC 数据中心)统一的云原生 Serverless 应用平台,首先我们来看一下 ChatGPT 4 会给出什么样的答案: 如何打造云上、云下统一的云原生 Serverless…

echarts图表用key强制刷新后空白

我的需求是echarts图表全屏后退出全屏在edge浏览器上没有什么问题但是在Chrome浏览器上会出现表格的线不能变回原来的比例的问题 我就想在退出全屏的时候强制刷新一下echarts图表外面的这个div useEffect(() > {if (col) {col.addEventListener("webkitfullscreenchan…

Windows系统安装TortoiseSVN并结合内网穿透实现远程访问本地服务器——“cpolar内网穿透”

文章目录 前言1. TortoiseSVN 客户端下载安装2. 创建检出文件夹3. 创建与提交文件4. 公网访问测试 前言 TortoiseSVN是一个开源的版本控制系统,它与Apache Subversion(SVN)集成在一起,提供了一个用户友好的界面,方便用…

Node.js基础---npm与包

包 概念:Node.js 中的第三方模块又叫做包 来源:由第三方个人或团队开发出来的,免费使用,且为开源 为什么需要:Node.js的内置模块只有一些底层API,开发效率低 包是基于内置模块封装出来的,提供更…

express+mysql+vue,从零搭建一个商城管理系统6--数据校验和登录

提示:学习express,搭建管理系统 文章目录 前言一、修改models/user.js二、修改routes下的user.js三、Api新建user/login接口四、删除数据库原有数据,添加新验证规则的用户四、用户登录总结 前言 需求:主要学习express,…

IP源防攻击IPSG(IP Source Guard)

IP源防攻击IPSG(IP Source Guard)是一种基于二层接口的源IP地址过滤技术,它能够防止恶意主机伪造合法主机的IP地址来仿冒合法主机,还能确保非授权主机不能通过自己指定IP地址的方式来访问网络或攻击网络。 2.1 IPSG基本原理 绑定…

c# 广度优先搜索(Breadth-First Search,BFS)

在这篇文章中我将讨论用于树和图的两种遍历机制之一。将使用 C# 示例介绍广度优先搜索 (BFS)。图是最具挑战性和最复杂的数据结构之一。 广度优先搜索的工作原理:广度优先搜索 (BFS)是一种探索树或图的方法。在 BFS 中,您首先探索…

Mac 重新安装系统

Mac 重新安装系统 使用可引导安装器重新安装(可用于安装非最新的 Mac OS,系统降级,需要清除所有数据) 插入制作好的可引导安装器(U盘或者移动固态硬盘),如何制作可引导安装器将 Mac 关机将 Ma…