链接未来:深入理解链表数据结构(一.c语言实现无头单向非循环链表)
在上一篇文章中,我们探索了顺序表这一基础的数据结构,它提供了一种有序存储数据的方法,使得数据的访 问和操作变得更加高效。想要进一步了解,大家可以移步于上一篇文章:探索顺序表:数据结构中的秩序之美
今天,我们将进一步深入,探讨另一个重要的数据结构——链表
链表和顺序表一样,都属于线性表,也用于存储数据,但其内部结构和操作方式有着明显的不同。通过C语言的具体实现,我们将会更加直观地理解它
源码可以打我的gitee里面查找:唔姆/比特学习过程2 (gitee.com)
文章目录
- @[toc]
- 一.链表的概念及结构
- 二.链表的分类
- 三.无头单向非循环链表的实现
- 1.项目文件规划
- 2.基本结构及功能一览
- 3.各功能接口具体实现
- 3.1打印单链表
- 3.2尾插
- 3.3头插
- 3.4尾删
- 3.5头删
- 3.6查找
- 3.7插入pos前一个
- 3.8删除pos前一个
- 3.9插入pos后一个
- 3.10删除pos后一个
- 3.11销毁(避免内存泄露)
文章目录
- @[toc]
- 一.链表的概念及结构
- 二.链表的分类
- 三.无头单向非循环链表的实现
- 1.项目文件规划
- 2.基本结构及功能一览
- 3.各功能接口具体实现
- 3.1打印单链表
- 3.2尾插
- 3.3头插
- 3.4尾删
- 3.5头删
- 3.6查找
- 3.7插入pos前一个
- 3.8删除pos前一个
- 3.9插入pos后一个
- 3.10删除pos后一个
- 3.11销毁(避免内存泄露)
一.链表的概念及结构
链表是一种物理存储(实际上)结构上==非连续、非顺序==(杂乱随意排序)的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
实际情况中:
从上图可发现:
- 链表在逻辑上连续,在物理上是不连续的
- 各个节点(Node)一般都是从堆上面申请空间的
- 从堆上面申请的空间是有一定策略的,可能连续,可也能不连续
二.链表的分类
- 单向或者双向
- 带头或者不带头
- 循环或者非循环
三种情况随意组合起来就有8种链表结构
其中,最为常用的是:
无头单向非循环和带头双向循环
无头单向非循环链表:结构简单,但是一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等
带头双向循环链表:结构最复杂,一般用在单独存储数。实际中使用的链表数据结构,都是带头双向循环链表。这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现它反而简单了
这两种结果都会给大家实现的,今天先来无头单向非循环链表
三.无头单向非循环链表的实现
1.项目文件规划
- 头文件SList.h:用来基础准备(常量定义,typedef),链表表的基本框架,函数的声明
- 源文件SList.h:用来各种功能函数的具体实现
- 源文件test.c:用来测试功能是否有问题,进行基本功能的使用
2.基本结构及功能一览
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>typedef int SLDataType;typedef struct SingleListNode
{int data;SingleListNode* next;
}SLNode;void SLPrint(SLNode* phead);// 单链表打印void SLPushBack(SLNode** pphead, int n);// 单链表尾插
void SLPushFront(SLNode** pphead, int n);// 单链表头插
void SLPopBack(SLNode** pphead);// 单链表尾删
void SLPopFront(SLNode** pphead);// 单链表尾删SLNode* SLFind(SLNode* phead, int n);
SLNode* SLInsert(SLNode** pphead, SLNode* pos, int n);//在pos前面插入
SLNode* SLErase(SLNode** pphead, SLNode* pos);//删除pos前面那个void SLInsertAfter(SLNode* pos, int n);//在pos后面插入
void SLEraseAfter(SLNode* pos);//在pos后面删除void SLDestory(SLNode** pphead);
3.各功能接口具体实现
3.1打印单链表
void SLPrint(SLNode* phead)
{assert(phead);SLNode* cur = phead;while (cur != NULL)//与while(cur)同样的效果{printf("%d ", cur->data);cur = cur->next;}printf("\n");
}
3.2尾插
SLNode* CreateNode(int n)
{SLNode* newNode= (SLNode*)malloc(sizeof(SLNode));if (newNode == NULL){perror("malloc error");return -1;}newNode->data = n;newNode->next = NULL;return newNode;
}void SLPushBack(SLNode** pphead, int n)
{assert(pphead);SLNode* newNode = CreateNode(n);//先把节点搞好//先考虑一下没有节点的情况if (*pphead == NULL){*pphead = newNode; //这就是传二级指针的原因://我们要改变 SLNode* phead本身的指向,就把他地址传过来//当我们只是要改变指向的结构体里的内容时只要传SLNode* phead就行了}else{SLNode* tail = *pphead;while (tail->next != NULL)//找到最后一个节点{tail = tail->next;}tail->next = newNode;}
}
- 通过
CreateNode
函数创建了一个含有数值n
的新节点newNode
- 然后根据链表是否为空进行不同的操作:
- 如果链表为空(即头指针指向空),则将新节点
newNode
赋值给头指针*pphead
- 如果链表不为空,则需要找到链表末尾的节点,通过遍历找到最后一个节点(tail),并将其
next
指针指向新节点newNode
,以将新节点插入到链表的末尾
为什么传入二级指针:
这种设计方式的原因在于需要修改指针本身的值,而不是只修改指针所指向的内容
考虑到单链表在插入节点时,可能会涉及链表头指针的修改,如果直接传递单级指针(指向头指针),在函数内部对头指针进行修改是不会反映到函数外部的==(形参是实参的临时拷贝)==。但如果使用二级指针,可以在函数内部修改指针的指向,这样修改后的指向会在函数外部保持
3.3头插
void SLPushFront(SLNode** pphead, int n)
{assert(pphead);SLNode* newNode = CreateNode(n);//先把节点搞好if (*pphead == NULL){*pphead = newNode;}else{newNode->next = (*pphead)->next;(*pphead)->next = newNode;}//或者//newNode->next = (*pphead);//*pphead = newNode;
}
- 通过
CreateNode
函数创建了一个含有数值n
的新节点newNode
- 接着,根据链表是否为空进行不同的操作:
- 如果链表为空(即头指针指向空),则将新节点
newNode
赋值给头指针*pphead
- 如果链表不为空,则将新节点
newNode
的next
指针指向当前头节点的下一个节点(原链表的第二个节点),然后将当前头节点的next
指针指向新节点newNode
,以完成插入
注释部分显示了另一种写法,通过先设置新节点的 next
指针指向当前头节点,然后再将链表的头指针指向新节点,实现了同样的插入操作
3.4尾删
void SLPopBack(SLNode** pphead)
{assert(pphead);assert(*pphead);//防止一个都没有还删if ((*pphead)->next == NULL)//只有一个{free(*pphead);*pphead = NULL;}else{//找到倒数第二个SLNode* pre_tail = *pphead;while (pre_tail->next->next != NULL){pre_tail = pre_tail->next;}free(pre_tail->next);pre_tail->next = NULL;}
}
- 检查链表头指针
*pphead
是否存在(不为 NULL),以及链表是否为空(只有一个节点)-
- 如果链表中只有一个节点,则直接释放该节点,并将链表头指针设置为 NULL,表示链表为空
- 如果链表中有多个节点,则会找到倒数第二个节点,即指向最后一个节点的前一个节点。它通过遍历链表直到找到倒数第二个节点
pre_tail
,然后释放最后一个节点,并将倒数第二个节点的next
指针设置为 NULL,表示该节点成为新的末尾节点
3.5头删
void SLPopFront(SLNode** pphead)
{assert(pphead);assert(*pphead);//防止一个都没有还删SLNode* first = (*pphead)->next;//一个和多个都适用free(*pphead);*pphead = first;
}
- 创建了一个临时指针
first
来指向原链表的第二个节点(如果存在)。这是因为要删除的是链表的头节点,为了不断开链表,需要先保存第二个节点的地址- 通过
free(*pphead)
释放掉原来的头节点,然后将链表的头指针*pphead
更新为原头节点的下一个节点first
3.6查找
SLNode* SLFind(SLNode* phead, int n)
{assert(phead);SLNode* cur = phead;while (cur != NULL)//与while(cur)同样的效果{if (cur->data == n){return cur;}cur = cur->next;}return NULL;
}
3.7插入pos前一个
void SLInsert(SLNode** pphead, SLNode* pos, int n)//在pos前面插入
{assert(pphead);assert(pos);SLNode* cur = *pphead;if (*pphead == pos)//在第一个节点前面插入{// 头插SLTPushFront(pphead, n);}else{while (cur->next != pos){cur = cur->next;}SLNode* newNode = CreateNode(n);newNode->next = cur->next;cur->next = newNode;}
}
- 如果要插入的位置
pos
就是链表的头节点*pphead
,即在第一个节点前面插入,则调用SLTPushFront
函数,直接在链表头部插入新节点newNode
- 如果要插入的位置不是头节点,则通过循环遍历链表,直到找到
pos
的前一个节点cur
,然后创建新节点newNode
并将其插入到pos
前面,完成节点的插入操作
3.8删除pos前一个
void SLErase(SLNode** pphead, SLNode* pos)
{assert(pphead);assert(pos);assert(*pphead != pos);//防止前面没有SLNode* cur = *pphead;SLNode* pre_cur = *pphead;while (cur->next != pos){pre_cur = cur;cur = cur->next;}pre_cur->next = pos;free(cur);cur = NULL;
}
3.9插入pos后一个
void SLInsertAfter(SLNode* pos, int n)
{assert(pos);SLNode* newNode =CreateNode(n);newNode->next = pos->next;pos->next = newNode;
}
- 创建一个新节点
newNode
,并将新节点的next
指针指向pos
节点原本的下一个节点,以保证链表的连续性- 将
pos
节点的next
指针指向新节点newNode
,完成了在指定节点之后插入新节点的操作
3.10删除pos后一个
void SLEraseAfter(SLNode* pos)
{assert(pos);SLNode* next = pos->next->next;free(pos->next);pos->next = NULL;pos->next = next;
}
3.11销毁(避免内存泄露)
void SLDestory(SLNode** pphead)
{assert(pphead);SLNode* cur = *pphead;SLNode* next = *pphead;while (cur!=NULL){next = cur->next;free(cur);cur = next;}*pphead = NULL;
}
循环删除每一个
Node
,最后把原本的结构体指针指向NULL
好啦,这次知识就先到这里啦!下一次大概率是双向带头循环的代码实现了。
相关文章:

链接未来:深入理解链表数据结构(一.c语言实现无头单向非循环链表)
在上一篇文章中,我们探索了顺序表这一基础的数据结构,它提供了一种有序存储数据的方法,使得数据的访 问和操作变得更加高效。想要进一步了解,大家可以移步于上一篇文章:探索顺序表:数据结构中的秩序之美 今…...

Python tkinter控件全集之组合选择框 ttk.ComboBox
Tkinter标准库 Tkinter是Python的标准GUI库,也是最常用的Python GUI库之一,提供了丰富的组件和功能,包括窗口、按钮、标签、文本框、列表框、滚动条、画布、菜单等,方便开发者进行图形界面的开发。Tkinter库基于Tk for Unix/Wind…...

Axure之中继器的使用(交互动作reperter属性Item属性)
目录 一.中继器的基本使用 二.中继器的动作(增删改查) 2.1 新增 2.2 删除 2.3 更新行 2.4 效果展示 2.5 模糊查询 三.reperter属性 在Axure中,中继器(Repeater)是一种功能强大的组件,用于创建重复…...

数字化医疗新篇章:构建智能医保支付购药系统
在迎接数字化医疗时代的挑战和机遇中,智能医保支付购药系统的建设显得尤为重要。本文将深入介绍如何通过先进的技术实现,构建一套智能、高效的医保支付购药系统,为全面建设健康中国贡献力量。 1. 引言 随着医疗科技的飞速发展,…...
11_12-Golang中的运算符
**Golang **中的运算符 主讲教师:(大地) 合作网站:www.itying.com** **(IT 营) 我的专栏:https://www.itying.com/category-79-b0.html 1、Golang 内置的运算符 算术运算符关系运算符逻辑运…...

k8s-ingress特性 9
TLS加密 创建证书 测试访问 auth认证 创建认证文件 rewrite重定向 进入域名时,会自动重定向到hostname.html 示例: 测试 版本的升级迭代,之前利用控制器进行滚动更新,在升级过程中无法做到快速回滚 更加平滑的升级࿱…...
【redis】redis系统实现发布订阅的标准模板
目录 简介参数配置代码模板 简介 Redis发布订阅功能是Redis的一种消息传递模式,允许多个客户端之间通过消息通道进行实时的消息传递。在发布订阅模式下,消息的发送者被称为发布者(publisher),而接收消息的客户端被称为…...

Python 时间日期处理库函数
标准库 datetime >>> import datetime >>> date datetime.date(2023, 12, 20) >>> print(date) 2023-12-20 >>> date datetime.datetime(2023, 12, 20) >>> print(date) 2023-12-20 00:00:00 >>> print(date.strfti…...

第二十二章 : Spring Boot 集成定时任务(一)
第二十二章 : Spring Boot 集成定时任务(一) 前言 本章知识点: 介绍使用Spring Boot内置的Scheduled注解来实现定时任务-单线程和多线程;以及介绍Quartz定时任务调度框架:简单定时调度器(Simp…...

关于“Python”的核心知识点整理大全32
目录 12.6.4 调整飞船的速度 settings.py ship.py alien_invasion.py 12.6.5 限制飞船的活动范围 ship.py 12.6.6 重构 check_events() game_functions.py 12.7 简单回顾 12.7.1 alien_invasion.py 12.7.2 settings.py 12.7.3 game_functions.py 12.7.4 ship.py …...

【krita】实时绘画 入门到精通 海报+电商+装修+人物
安装插件 首先打开comfyUI,再打开krita,出现问题提示, 打开 cd custom_nodes 输入命令 安装控件 git clone https://github.com/Acly/comfyui-tooling-nodes.git krita基础设置 设置模型 设置lora (可设置lora强度 增加更多…...

云原生系列2-CICD持续集成部署-GitLab和Jenkins
1、CICD持续集成部署 传统软件开发流程: 1、项目经理分配模块开发任务给开发人员(项目经理-开发) 2、每个模块单独开发完毕(开发),单元测试(测试) 3、开发完毕后,集成部…...

50ms时延工业相机
华睿工业相机A3504CG000 参数配置: 相机端到端理论时延:80ms 厂家同步信息,此款设备帧率上线23fps,单帧时延:43.48ms,按照一图缓存加上传输显示的话,厂家预估时延在:80ms 厂家还有…...

CPU缓存一致性问题
什么是可见性问题? Further Reading :什么是可见性问题? 缓存一致性 内存一致性 内存可见性 顺序一致性区别 CPU缓存一致性问题 由于CPU缓存的出现,很好地解决了处理器与内存速度之间的矛盾,极大地提高了CPU的吞吐能…...
35道HTML高频题整理(附答案背诵版)
1、简述 HTML5 新特性 ? HTML5 是 HTML 的最新版本,它引入了很多新的特性和元素,以提供更丰富的网页内容和更好的用户体验。以下是一些主要的新特性: 语义元素:HTML5 引入了新的语义元素,像 <article&g…...

【powershell】Windows环境powershell 运维之历史文件压缩清理
🦄 个人主页——🎐开着拖拉机回家_Linux,大数据运维-CSDN博客 🎐✨🍁 🪁🍁🪁🍁🪁🍁🪁🍁 🪁🍁🪁&am…...

【Linux】Linux线程概念和线程控制
文章目录 一、Linux线程概念1.什么是线程2.线程的优缺点3.线程异常4.线程用途5.Linux进程VS线程 二、线程控制1.线程创建2.线程终止3.线程等待4.线程分离 一、Linux线程概念 1.什么是线程 线程是进程内的一个执行流。 我们知道,一个进程会有对应的PCB,…...

Flink cdc3.0同步实例(动态变更表结构、分库分表同步)
文章目录 前言准备flink环境docker构建mysql、doris环境数据准备 通过 FlinkCDC cli 提交任务整库同步同步变更路由变更路由表结构不一致无法同步 结尾 前言 最近Flink CDC 3.0发布, 不仅提供基础的数据同步能力。schema 变更自动同步、整库同步、分库分表等增强功…...

国产Apple Find My认证芯片哪里找,伦茨科技ST17H6x芯片可以帮到您
深圳市伦茨科技有限公司(以下简称“伦茨科技”)发布ST17H6x Soc平台。成为继Nordic之后全球第二家取得Apple Find My「查找」认证的芯片厂家,该平台提供可通过Apple Find My认证的Apple查找(Find My)功能集成解决方案。…...
肺癌相关知识
写在前面 大概想了解下肺癌相关的知识,开此贴做记录,看看后续有没有相关的生信文章思路。 综述 文章名期刊影响因子Lung cancer immunotherapy: progress, pitfalls, and promisesMol Cancer37.3 常见治疗手段有surgery, radiation therapy, chemoth…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

高考志愿填报管理系统---开发介绍
高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发,采用现代化的Web技术,为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## 📋 系统概述 ### 🎯 系统定…...

一些实用的chrome扩展0x01
简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...
Django RBAC项目后端实战 - 03 DRF权限控制实现
项目背景 在上一篇文章中,我们完成了JWT认证系统的集成。本篇文章将实现基于Redis的RBAC权限控制系统,为系统提供细粒度的权限控制。 开发目标 实现基于Redis的权限缓存机制开发DRF权限控制类实现权限管理API配置权限白名单 前置配置 在开始开发权限…...