单链表(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;
}

运行结果:

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

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

相关文章

Day65 代码随想录打卡|回溯算法篇---组合总和II

题目(leecode T40): 给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意:解集不能包含…

Linux文件编程(打开/创建写入读取移动光标)

目录 一、如何在Linux下做开发 1.vi编辑器 2.gcc编译工具 3.常用指令 二、文件打开及创建 三、写入文件 四、读取文件 五、文件“光标”位置 一、如何在Linux下做开发 所谓文件编程,就是对文件进行操作,Linux的文件和Windows系统的文件大差不差…

Python函数 之 函数基础

print() 在控制台输出 input() 获取控制台输⼊的内容 type() 获取变量的数据类型 len() 获取容器的⻓度 (元素的个数) range() ⽣成⼀个序列[0, n) 以上都是我们学过的函数,函数可以实现⼀个特定的功能。我们将学习⾃⼰如何定义函数, 实现特定的功能。 1.函数是什么…

氛围感视频素材高级感的去哪里找啊?带氛围感的素材网站库分享

亲爱的创作者们,大家好!今天我们来聊聊视频创作中至关重要的一点——氛围感。一个好的视频,不仅要有视觉冲击力,还要能够触动观众的情感。那我们应该去哪里寻找这些充满氛围感且高级的视频素材呢?别急,我这…

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

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

混合贪心算法求解地铁线路调度

一、问题描述 城市轨道交通的繁荣发展,带来了车辆资源需求的日益增加。如何兼顾运营服务水平和运营成本,以最少的车底优质地完成运输任务成为一大严峻问题。本题在后续的描述中将由多辆动车和拖车组合而成的车组称为车底。在日常的运营组织中&#xff0…

【文档智能】LACE:帮你自动生成文档布局的方法浅尝

前言 往期很多文章都介绍了【文档智能】上布局识别(版式分析)的技术思路,版式分析是通过对文档版式进行布局识别,识别文档中的元素类型的过程。这次来看看一个有趣的思路,通过已有的元素类型,来生成可控的…

赠你一只金色的眼 - 富集分析和表达数据可视化

GOplot包介绍 GOplot包用于生物数据的可视化。更确切地说,该包将表达数据与功能分析的结果整合并进行可视化。但是要注意该包不能用于执行这些分析,只能把分析结果进行可视化。在所有科学领域,由于空间限制和结果所需的简洁性,切…

Agent如何帮助大模型“增强记忆”?

Agent如何帮助大模型“增强记忆”? 原创 格林 神州问学 2024年07月08日 17:50 日本 记忆反馈 >规划? 来源|神州问学 引言 去年6月份,Lilian发布了关于LLM驱动的Agent的结构和组件,其中包括规划、行动、工具还有记忆&#xff…

带有子节点的树状表的父节点拖动排序#Vue3#Sortable插件

带有子节点的树状表的父节点拖动排序#Vue3#Sortable插件 使用Sortable插件这里要保证获取到的是父节点的下标&#xff0c;属性newDraggableIndex获取到的就是只有父节点的下标。设置子节点不能被拖动&#xff0c;最后在逐个调用接口进行数据库中顺序的更新。 <template>…

【Python】已解决:SyntaxError: invalid character in identifier

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;SyntaxError: invalid character in identifier 一、分析问题背景 在Python编程中&#xff0c;SyntaxError: invalid character in identifier是一个常见的编译…

面经-计算机网络-数据结构-堆

1.什么是堆 堆是一种满足以下条件的树&#xff1a; 堆中的每一个节点值都大于等于&#xff08;或小于等于&#xff09;子树中所有节点的值。或者说&#xff0c;任意一个节点的值都大于等于&#xff08;或小于等于&#xff09;所有子节点的值。 2.堆的用途 当我们只关心所有数…

tk Text文本框赋值,清空

import tkinter as tk# 创建主窗口 root tk.Tk() root.title("文本框内容赋值示例")# 创建一个Text小部件 text_area tk.Text(root, height10, width50) text_area.pack()# 将内容赋值给Text小部件 text_area.insert(tk.END, "这是文本框中的内容。\n")#…

android CameraX构建相机拍照

Android CameraX 是一个 Jetpack 支持库&#xff0c;旨在简化相机应用的开发工作。它提供了一致且易用的API接口&#xff0c;适用于大多数Android设备&#xff0c;并可向后兼容至Android 5.0&#xff08;API级别21&#xff09;。 CameraX解决了在多种设备上实现相机功能时所遇…

26.5 Django模板层

1. 模版介绍 在Django中, 模板(Templates)主要用于动态地生成HTML页面. 当需要基于某些数据(如用户信息, 数据库查询结果等)来动态地渲染HTML页面时, 就会使用到模板.以下是模板在Django中使用的几个关键场景: * 1. 动态内容生成: 当需要根据数据库中的数据或其他动态数据来生…

linux使用chattr与lsattr设置文件/目录防串改

背景 linux服务器下,防止某个文件/目录被串改(增删改),可以使用chattr与lsattr设置,这是一种保护机制,用于防止意外地修改或删除重要的文件内容。 chattr与lsattr使用 1.设置目录 图中/tmp/zhk,设置目录属性文件可能被设置为不可更改(immutable)或者只追加(append …

1.Frida框架-Frida环境搭建

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;微尘网校 前置条件&#xff1a;需要科学上网 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F…

python—读写csv文件

目录 csv库方法参数 读取数据 csv.reader方法 文件指定行或列数据读取操作 txt文件的readlines、read方法 csv.DictReader方法 写入数据 txt文件的write&#xff0c;writelines csv.writer方法 csv.DictWriter方法 读写联合(修改及插入数据) 读写csv 文件时&#xf…

【Notepad】Notepad_6.3.1 的中文版安装详情

目录 &#x1f33c;1. Notepad的认识 &#x1f33c;2. Notepad中文版安装详情 &#x1f33c;1. Notepad的认识 Notepad 是 Windows 操作系统中的一个文本编辑器程序&#xff0c;通常用于创建和编辑简单的文本文件&#xff0c;如文本文档 (.txt)。它非常轻量且功能简单&#…

【本地docker启动私有大模型】

一、最终效果 中英文对话 生成代码 二、资源配置 本文选择的模型运行内存需要 4G&#xff0c;因此宿主机建议内存大于8G&#xff0c;CPU建议 6 核以上&#xff1b; 参考博主该mac配置可以相对流畅运行。只需要 CPU资源&#xff0c;不需要 GPU。 三、搭建步骤 启动docker容…