单链表(C语言详细版)

1. 链表的概念及结构

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

链表的结构跟火车车厢相似,淡季时车次的车厢会相应减少,旺季时车次的车厢会额外增加几节。只需要将火车里的某节车厢去掉或加上,不会影响其他车厢,每节车厢都是独立存在的。

车厢是独立存在,且每节车厢都有车门。想象一下这样的场景,假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下如何从车头走到车尾?

最简单的做法:每节车厢里都放一把下一节车厢的钥匙。

在链表里,每节“车厢”是什么样的呢?

与顺序表不同的是,链表里的每节“车厢”都是独立申请下来的空间,我们称之为“结点/节点”。节点的组成主要有两个部分:当前节点要保存的数据和保存下一个节点的地址(指针变量)。图中指针变量 plist 保存的是第一个节点的地址,我们称 plist 此时“指向”第一个节点,如果我们希望 plist “指向”第二个节点时,只需要修改 plist 保存的内容为 0x0012FFA0。

为什么还需要指针变量来保存下一个节点的位置?

链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点。

假设当前保存的节点为整型:

// 定义节点的结构
// 数据 + 指向下一个节点的指针
typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;		// 存储的节点数据
	struct SListNode* next; // 指针变量用来保存下一个节点的地址
}SLTNode;

当我们想要保存一个整型数据时,实际是向操作系统申请了一块内存,这个内存不仅要保存整型数据,也需要保存下一个节点的地址(当下一个节点为空时保存的地址为空)。

当我们想要从第一个节点走到最后一个节点时,只需要在前一个节点拿上下一个节点的地址(下一个节点的钥匙)就可以了。

给定的链表结构中,如何实现节点从头到尾的打印?

补充:

1. 链式结构在逻辑上是连续的,在物理结构上不一定连续;

2. 节点一般是从堆上申请的;

3. 从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能是连续,可能不连续。

2. 单链表的实现

2.1 链表的打印

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

测试程序:首先我们得先开辟新的空间,然后创建几个新的节点,再把所创建的新节点关联起来,最后调用打印函数接口,打印数据(注意:最后一个节点存储的下一个节点的地址要为NULL

void SListTest01()
{
	// 链表是由一个一个的节点组成
	// 创建几个节点
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	node1->data = 1;

	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	node2->data = 2;

	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	node3->data = 3;

	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
	node4->data = 4;

	// 将四个节点连接起来
	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	// 调用链表的打印
	SLTNode* plist = node1;
	SLTPrint(plist);
}

int main()
{
	SListTest01();

	return 0;
}

运行结果:

2.2 链表的尾插

// 链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);

如果我们要在链表的最后一个节点尾插一个新的节点,首先要先找到尾节点,再将尾节点和新节点连接起来。不过,在插入之前我们得先申请一个新的节点空间。这里需要注意的一个点:我们形参要改变实参必须要传地址(即要用指针来接收),那为什么形参不用一级指针而用二级指针呢?因为,我们创建的节点是一个结构体指针 SLTNode* ,所以我们得用二级指针来接收。

知道了原理,我们接下来就可以编写程序,首先得判断头节点的地址 &plist (pphead)不能为空,然后申请一个新的节点空间。好,接下来我们要分两种情况:1. 链表是空链表;2. 链表是非空链表。空链表:我们直接把新节点作为头节点;非空链表:我们就正常的尾插。

尾插接口函数:

// 申请节点函数
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newNode == NULL)
	{
		perror("malloc fail!");
		exit(1);	// 正常退出是0,非正常退出是非0
	}
	newNode->data = x;
	newNode->next = NULL;

	return newNode;
}

// 链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);	// pphead 不能为NULL
	// *pphead 就是指向第一个节点的指针
	// 空链表和非空链表
	// 申请新的节点
	SLTNode* newNode = SLTBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		// 找尾
		SLTNode* ptail = *pphead;
		while (ptail->next)	// ptail->next != NULL
		{
			ptail = ptail->next;
		}
		// ptail指向的就是尾节点
		ptail->next = newNode;
	}
}

测试程序:尾插 1 2 3 4

void SListTest02()
{
	SLTNode* plist = NULL;
	// 测试尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
}

int main()
{
	SListTest02();

	return 0;
}

运行结果:

2.3 链表的头插

// 链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);

头插我们也得分两种情况:1. 链表是空链表;2. 链表是非空链表。

首先得判断头节点的地址 &plist (pphead)不能为空,然后申请一个新的节点空间。让新节点 newNode 的下一个节点的地址 newNode->next 指向头节点 *pphead ,再新节点 newNode 作为新的头节点 *pphead。

头插接口函数:

// 链表的头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	// 空链表和非空链表都能使用此方法
	assert(pphead);	// pphead 不能为NULL
	// 申请新的节点
	SLTNode* newNode = SLTBuyNode(x);
	newNode->next = *pphead;
	*pphead = newNode;
}

测试程序:尾插 1 2 3 4,再头插 6 7 8

void SListTest02()
{
	SLTNode* plist = NULL;
	// 测试尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	// 测试头插
	SLTPushFront(&plist, 6);
	SLTPrint(plist);
	SLTPushFront(&plist, 7);
	SLTPrint(plist);
	SLTPushFront(&plist, 8);
	SLTPrint(plist);
}

int main()
{
	SListTest02();

	return 0;
}

运行结果:

我们测试一下空链表是不是也可行:

void SListTest02()
{
	SLTNode* plist = NULL;

	// 测试头插
	SLTPushFront(&plist, 6);
	SLTPrint(plist);
	SLTPushFront(&plist, 7);
	SLTPrint(plist);
	SLTPushFront(&plist, 8);
	SLTPrint(plist);
}

int main()
{
	SListTest02();

	return 0;
}

运行结果:可行!

2.3 链表的尾删

// 链表的尾删
void SLTPopBack(SLTNode** pphead);

链表节点的删除不是单纯把要删除的节点给free掉,而是要找最后一个节点的前一个节点,把这个节点的下一个节点的地址置为NULL。

尾删我们也要分两种情况:1. 只有单节点;2. 多节点。单节点:我们直接把头节点给释放掉,然后置为空。多节点:我们先找到最后一个节点,怎么找呢?定义一个 ptail 结构体指针,遍历链表,当 patil->next 为 NULL ,说明此时的 ptail 就是尾节点。我们还要找尾节点的前一个节点,这时我们就要定义一个 prev 结构体指针,把找尾节点的 prev = ptail 保存一下,最后 patil = NULL,说明前一次找的就是尾节点的前一个节点。

// 链表的尾删
void SLTPopBack(SLTNode** pphead)
{
	// 链表不能为空
	assert(pphead && *pphead);

	// 链表只有一个节点
	if ((*pphead)->next == NULL)	// -> 优先级高于 *
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		// 链表有多个节点
		SLTNode* prev = *pphead;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
}

测试程序:尾插 1 2 3 4,然后尾删,一直删到只剩一个节点,多节点和单节点一起判断

void SListTest02()
{
	SLTNode* plist = NULL;
	// 测试尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

    // 测试尾删
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
}

int main()
{
	SListTest02();

	return 0;
}

运行结果:

2.4 链表的头删

// 链表的头删
void SLTPopFront(SLTNode** pphead);

在释放头节点之前,我们要先用一个指针 next 保存头节点 *pphead 下一个节点的地址,然后释放头节点,再把 next 指针作为新的头节点。

// 链表的头删
void SLTPopFront(SLTNode** pphead)
{
	// 链表不能为空
	assert(pphead && *pphead);

	// 多个节点和单个节点都能使用
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

测试程序:

void SListTest02()
{
	SLTNode* plist = NULL;
	// 测试尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

    // 测试头删
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
}

int main()
{
	SListTest02();

	return 0;
}

运行结果:尾插 1 2 3 4,然后头删,一直删到只剩一个节点,多节点和单节点一起判断

2.5 链表的查找

// 链表的查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

遍历链表数据,找到了,就返回对应的结构体指针,没有找到,就返回NULL。

// 链表的查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

测试程序:

void SListTest02()
{
	SLTNode* plist = NULL;
	// 测试尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);

    // 测试查找
	SLTNode* find = SLTFind(plist, 3);
	if (find == NULL)
	{
		printf("没有找到!\n");
	}
	else
	{
		printf("找到了!\n");
	}
}

int main()
{
	SListTest02();

	return 0;
}

运行结果:

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

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

相关文章

elasticsearch SQL:在Elasticsearch中启用和使用SQL功能

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

苍穹外卖--导入分类模块功能代码

把各层代码拷贝到所需文件夹下&#xff0c; 进行编译 在运行 提交和推送仓库

C++:C++入门基础|命名空间|输入输出

欢迎来到HarperLee的学习笔记&#xff01; 博主主页传送门&#xff1a; HarperLee的博客主页! 想要一起进步的uu来后台哦&#xff01; 一、什么是C? 在此之前&#xff0c;我们所学习的C语言是一种结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&a…

【STM32】MDK的编译过程及文件类型全解

1.编译过程简介 编译&#xff1a;MDK软件使用的编译器是armcc和armasm&#xff0c; 它们根据每个c/c和汇编源文件编译成对应的以“.o”为后缀名的对象文件(Object Code&#xff0c;也称目标文件)&#xff0c; 其内容主要是从源文件编译得到的机器码&#xff0c;包含了代码、数据…

怎么压缩ppt?这几种压缩方法大家都在用!

怎么压缩ppt&#xff1f;当我们沉浸在PPT创作的海洋中&#xff0c;每一个精心的布局、每一个动人的动画&#xff0c;都仿佛是我们心血的结晶&#xff0c;然而&#xff0c;随着我们不断雕琢&#xff0c;PPT文件的大小也在悄然增长&#xff0c;如同一只隐形的巨兽&#xff0c;在不…

无线充电宝哪个牌子好?绿联、西圣、小米充电宝测评对比!

随着科技的不断进步和智能设备的普及&#xff0c;无线充电宝逐渐成为了现代人生活中的必需品。它们不仅方便了我们的日常充电需求&#xff0c;更减少了线缆的束缚&#xff0c;提高了使用的便捷性。在众多品牌中&#xff0c;绿联、西圣和小米作为市场上广受好评的无线充电宝品牌…

Windows下载、配置Java JDK开发环境的方法

本文介绍在Windows电脑中&#xff0c;安装JDK&#xff08;Java Development Kit&#xff09;&#xff0c;也就是Java开发工具包的详细方法。 JDK是Java软件开发的基础&#xff0c;由Oracle公司提供&#xff0c;用于构建在Java平台上运行的应用程序与组件等&#xff1b;其已经包…

【windows】安装抓包工具Burp Suite 2024激活汉化

前言 在项目即将上线阶段&#xff0c;迈入生产环境之际&#xff0c;确保其安全性成为我们不可忽视的首要任务。为筑起一道坚不可摧的安全防线&#xff0c;我们借助业界公认的网络安全利器——Burp Suite&#xff0c;我们将展开一场全面的安全测试&#xff0c;旨在探查并消除任…

旗晟机器人AI智能算法有哪些?

在当今迅猛发展的工业4.0时代&#xff0c;智能制造和自动化运维已然成为工业发展至关重要的核心驱动力。伴随技术的持续进步&#xff0c;工业场景中的运维巡检已不再单纯地依赖于传统的人工运维方式&#xff0c;而是愈发多地融入了智能化的元素&#xff0c;其中智能巡检运维系统…

数据库MySQL---基础篇

存储和管理数据的仓库 MySQL概述 数据库相关概念 数据库&#xff08;DataBase&#xff09;---数据存储的仓库&#xff0c;数据是有组织的进行存储 数据库管理系统&#xff08;DBMS&#xff09;-----操纵和管理数据库的大型软件 SQL----操作关系型数据库的编程语言&#xff…

文华财经盘立方多空变色波段趋势线指标公式源码

文华财经盘立方多空变色波段趋势线指标公式源码&#xff1a; N1:20; N2:ROUND(N1/2,1); N3:ROUND(SQRT(N1),1); N4:2*EMA2(C,N2)-EMA2(C,N1); 尊重市场:EMA2(N4,N3),COLORRED,LINETHICK2; 尊重市场1:IF(尊重市场<REF(尊重市场,1), 尊重市场,NULL),COLORGREEN,LINETHIC…

gitlab-runner安装部署CI/CD

手动安装 卸载旧版&#xff1a; gitlab-runner --version gitlab-runner stop yum remove gitlab-runner下载gitlab对应版本的runner # https://docs.gitlab.com/runner/install/bleeding-edge.html#download-any-other-tagged-releasecurl -L --output /usr/bin/gitlab-run…

打开IDEA,程序员思考的永远只有两件事!!!

微信公众号&#xff1a;牛奶 Yoka 的小屋 有任何问题。欢迎来撩~ 最近更新&#xff1a;2024/07/09 [大家好&#xff0c;我是牛奶。] 当年面试时背了很多八股文&#xff0c;但在日渐重复的机械工作中&#xff08;产品业务开发&#xff09;&#xff0c;计算机网络、操作系统、算…

leetcode:LCR 018. 验证回文串(python3解法)

难度&#xff1a;简单 给定一个字符串 s &#xff0c;验证 s 是否是 回文串 &#xff0c;只考虑字母和数字字符&#xff0c;可以忽略字母的大小写。 本题中&#xff0c;将空字符串定义为有效的 回文串 。 示例 1: 输入: s "A man, a plan, a canal: Panama" 输出: t…

编辑器 goland 和 visual studio code

goland 编辑器做的真是太好了&#xff0c;面向 go 代码的定制设计&#xff0c;但它是收费软件&#xff0c;价格还贵的超出了自己的经济能力范围。有时候想打几行代码&#xff0c;却没有趁手的兵器&#xff0c;真是难受。但求助免费破解版吧&#xff0c;又需要关注公众号&#x…

Lab1 论文 MapReduce

目录 &#x1f339;前言 &#x1f985;2 Programming Model &#x1f33c;2.1 Example &#x1f33c;2.2 Types &#x1f33c;2.3 More Examples &#x1f985;3 Implementation(实现) &#x1f33c;3.1 ~ 3.3 &#x1f33c;3.4 ~ 3.6 &#x1f985;4 Refinemen…

Java-Redis-Clickhouse-Jenkins-MybatisPlus-Zookeeper-vscode-Docker-jdbc-xxljob

文章目录 Clickhouse基础实操windows docker desktop 下载clickhousespringboot项目配置clickhouse Redis谈下你对Redis的了解&#xff1f;Redis一般都有哪些使用的场景&#xff1f;Redis有哪些常见的功能&#xff1f;Redis支持的数据类型有哪些&#xff1f;Redis为什么这么快…

爬虫怎么实现抓取的

1.4爬虫工程师常用的库通过图1-3我们了解到&#xff0c;爬虫程序的完整链条包括整理需求、分析目标、发出网络请求、文本解析、数据入库和数据出库。其中与代码紧密相关的有&#xff1a;发出网络请求、文本解析、数据入库和数据出库&#xff0c;接下来我们将学习不同阶段中爬虫…

Proxmox VE 8虚拟机直通USB磁盘

作者&#xff1a;田逸&#xff08;fromyz&#xff09; 今天有个兄弟发消息&#xff0c;咨询怎么让插在服务器上的U盾被Proxmox VE上的虚拟机识别。在很久很久以前&#xff0c;我尝试过在Proxmox VE 5以前的版本创建windows虚拟机&#xff0c;并把插在Proxmox VE宿主机上的银行U…

[ TOOLS ] JFLASH 使用说明

一、使用everything查找JFLASH everything是指这个软件&#xff0c;使用这个方便查找想要的文件 二、创建一个工程并配置 创建完后进行配置&#xff1a; Target devic: 板子的芯片型号&#xff0c;比如R7FA6M4Target interface: 一般是SWDSpeed: 一般是4000kHz, 不能下载则将Sp…