数据结构--单链表
前言
上一章,我们讲了数据结构--动态顺序表,我们会发现有以下问题:
1.当我们要头部或者插入或删除时,都需要进行位置挪动,腾出某一个位置,时间复杂度为0(N);
2.增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3.增容会有一定的浪费空间;例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
下面我们来看看单链表这种线性结构;
链表
概念与结构
链表是一种常见的数据结构,用于存储和组织数据。它由一系列节点组成,每个节点包含两部分:数据和指向下一个节点的指针。
链表中的节点在内存中可以分布在任意位置,不像数组那样需要连续的存储空间。每个节点都包含了存储的数据以及指向下一个节点的指针。通过这种方式,链表可以灵活地分配和管理内存空间。就像一节节连动的火车车厢;

在数据结构中,呈现:
逻辑图中,呈现:

在逻辑图中,链式结构是连续性的,但实际上不一样连续;从数据结构中看出,链表是通过地址来联系在一起的,不需要地址的连续性;在我们要解决链表相关问题时,只需要画出逻辑图即可;
注意:
结点的空间在堆区中开辟;堆区中申请出的空间,会按照一定的策略进行分配,两次申请的空间可能连续,可能不连续;
链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向或者双向
2. 带头或者不带头

3. 循环或者非循环

可以通过一定的组合达成不同种类的链表;
这里我们比较常用的是有两种结构:
在这里,我们将先实现无头单向非循环链表,这是链表中结构最为简单的;简称单链表。
单链表的接口实现
先写一下它的结构:
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>typedef int SLTDataType;
typedef struct SLTNode
{SLTDataType data;struct SListNode* next;}SLTNode;
结构体中放入一个数据存储类型和一个结构体指针;结构体指针存放下一个结点的地址;
单链表打印
void SLTrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}
将链表从头到尾遍历一遍,用一个cur指针来进行移动,在while循环中不断遍历打印出结果;打印完就进入下一个结点;
增加链表结点
SLTNode* BuySListNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("mallco fail");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}
用动态内存分配进行扩容,同时对data和next进行初始化;最后返回结点;
尾插
void SLPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySListNode(x);if (* pphead == NULL){* pphead = newnode;}else{SLTNode* tail = * pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}}
这里要注意,我们的形参用到了二级指针,因为当结构体指针为空时,我们就需要对结构体指针进行改变,用二级指针接收结构体指针的地址,能够有效的访问,否则将会报错;当结构体指针不为空时,就利用结构体指针通过循环访问到尾结点,然后在尾结点进行连接;
验证:
void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);}
int main()
{Test3();return 0;
}

尾删
void SLPopBack(SLTNode** pphead)
{assert(pphead);//判空assert(*pphead);//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}//其他else{SLTNode* tailPrev = NULL;SLTNode* tail = *pphead;while (tail->next){tailPrev = tail;tail = tail->next;}free(tail);tailPrev->next = NULL;}
}
当删除的是第一个结点,将会改变结构体指针的地址,所以形参要引用二级指针;其他情况就先找到尾结点,然后进行删除;
验证:
void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);SLPopBack(&plist);SLTrint(plist);}
int main()
{Test3();return 0;
}

头插头删
void SLPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;}void SLPopFront(SLTNode** pphead)
{ assert(pphead);//判空assert(*pphead);//其他SLTNode* newhead = (*pphead)->next;free(*pphead);*pphead = newhead;
}
头插相对尾插来说比较容易,因为有头指针,所以不用遍历循环来找到尾结点;并且无论头节点是否为空,操作程序都保持一致;
头删只要找到头结点的下一个结点,那么就可以删除了;
验证:
void Test2()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLPushBack(&plist, 5);SLTrint(plist);SLPushFront(&plist, 6);SLPushFront(&plist, 7);SLPushFront(&plist, 8);SLPushFront(&plist, 9);SLTrint(plist);SLPopFront(&plist);SLTrint(plist);}int main()
{Test2();return 0;
}

查找与插入
SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{//判空assert(phead);SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySListNode(x);newnode->next = pos->next;pos->next = newnode;}
查找:在循环里面通过结点的data与x进行匹配,找到就返回该结点,找不到返回空;如果有多个结点的data与x一致,返回链表最接近头指针的;
插入:是在pos后面进行插入,这样插入比较方便,不用考虑头指针是否为空的问题;
验证:
void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);SLTNode* pos = SLFind(plist, 3);SLTInsertAfter(pos, 88);SLTrint(plist);}
int main()
{Test3();return 0;
}

删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pos);if (pos == *pphead){SLPopFront(pphead);}else{SLTNode* perv = *pphead;while (perv->next != pos){perv = perv->next;}perv->next = pos->next;free(pos);}
}void SLTEraseAfter(SLTNode* pos)
{assert(pos);//检查尾节点assert(pos->next);SLTNode* posNext = pos->next;pos->next = posNext->next;free(posNext);}
第一种删除是删除pos结点,但需要判断该结点是否为首结点;而且需要遍历找到pos结点的前一个结点;比较麻烦;
第二种删除是删除pos结点后一个结点,只需要通过pos结点连接到下下一个结点即可,最后free掉pos的下一个结点;
验证:
void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);SLTNode* pos = SLFind(plist, 3);SLTInsertAfter(pos, 88);SLTrint(plist);SLTErase(&plist, pos);SLTrint(plist);}
int main()
{Test3();return 0;
}

void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);SLTNode* pos = SLFind(plist, 3);SLTInsertAfter(pos, 88);SLTrint(plist);SLTEraseAfter(pos);SLTrint(plist);}
int main()
{Test3();return 0;
}

摧毁
void SLTDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur){SLTNode* prev = cur;cur = cur->next;free(prev);}*pphead = NULL;
}
通过记住头结点的下一个结点,free掉头节点,然后头节点的下一个结点成为新的头节点;
验证:
void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);SLTNode* pos = SLFind(plist, 3);SLTInsertAfter(pos, 88);SLTrint(plist);SLTDestroy(&plist);SLTrint(plist);
}
int main()
{Test3();return 0;
}

完整代码
slist.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>typedef int SLTDataType;
typedef struct SLTNode
{SLTDataType data;struct SListNode* next;}SLTNode;void SLTrint(SLTNode* phead);
SLTNode* BuySListNode(SLTDataType x);
void SLPushBack(SLTNode** pphead, SLTDataType x);
void SLPushFront(SLTNode** pphead, SLTDataType x);
void SLPopBack(SLTNode** pphead);
void SLPopFront(SLTNode** pphead);
SLTNode* SLFind(SLTNode* phead, SLTDataType x);
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
void SLTErase(SLTNode** pphead, SLTNode* pos);
void SLTEraseAfter(SLTNode* pos);
void SLTDestroy(SLTNode** phead);
slist.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Slist.h"void SLTrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur){printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}SLTNode* BuySListNode(SLTDataType x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("mallco fail");exit(-1);}newnode->data = x;newnode->next = NULL;return newnode;
}void SLPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySListNode(x);if (* pphead == NULL){* pphead = newnode;}else{SLTNode* tail = * pphead;while (tail->next != NULL){tail = tail->next;}tail->next = newnode;}}void SLPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;}void SLPopBack(SLTNode** pphead)
{assert(pphead);//判空assert(*pphead);//一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}//其他else{SLTNode* tailPrev = NULL;SLTNode* tail = *pphead;while (tail->next){tailPrev = tail;tail = tail->next;}free(tail);tailPrev->next = NULL;}
}
void SLPopFront(SLTNode** pphead)
{ assert(pphead);//判空assert(*pphead);//其他SLTNode* newhead = (*pphead)->next;free(*pphead);*pphead = newhead;
}SLTNode* SLFind(SLTNode* phead, SLTDataType x)
{//判空assert(phead);SLTNode* cur = phead;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = BuySListNode(x);newnode->next = pos->next;pos->next = newnode;}void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pos);if (pos == *pphead){SLPopFront(pphead);}else{SLTNode* perv = *pphead;while (perv->next != pos){perv = perv->next;}perv->next = pos->next;free(pos);}
}void SLTEraseAfter(SLTNode* pos)
{assert(pos);//检查尾节点assert(pos->next);SLTNode* posNext = pos->next;pos->next = posNext->next;free(posNext);}void SLTDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* cur = *pphead;while (cur){SLTNode* prev = cur;cur = cur->next;free(prev);}*pphead = NULL;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Slist.h"void Test1()
{int n;SLTNode* plist = NULL;printf("请输入链表长度");scanf("%d", &n);printf("请输入值");for (int i = 0; i < n; i++){int val;scanf("%d", &val);SLTNode* newnode = BuySListNode(val);newnode->next = plist;plist = newnode;}SLTrint(plist);
}void Test2()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLPushBack(&plist, 5);SLTrint(plist);SLPushFront(&plist, 6);SLPushFront(&plist, 7);SLPushFront(&plist, 8);SLPushFront(&plist, 9);SLTrint(plist);SLPopFront(&plist);SLTrint(plist);}void Test3()
{SLTNode* plist = NULL;SLPushBack(&plist, 1);SLPushBack(&plist, 2);SLPushBack(&plist, 3);SLPushBack(&plist, 4);SLTrint(plist);SLTNode* pos = SLFind(plist, 3);SLTInsertAfter(pos, 88);SLTrint(plist);SLTDestroy(&plist);SLTrint(plist);
}
int main()
{Test3();return 0;
}
相关文章:
数据结构--单链表
前言 上一章,我们讲了数据结构--动态顺序表,我们会发现有以下问题: 1.当我们要头部或者插入或删除时,都需要进行位置挪动,腾出某一个位置,时间复杂度为0(N); 2.增容需要申请新空间,…...
过程:从虚拟机上添加 git 并成功提交到 GitLab 的全过程
Ⅰ、准备工作: 1、Git 查看: 其一、命令:git --version // 此时就能在虚拟机环境下看到 git 的版本为: git version 2.41.0 其二、如何在虚拟机上安装 git : A、命令 : sudo apt-get install git B、然后再输入虚…...
机器学习笔记之优化算法(九)收敛速度的简单认识
机器学习笔记之优化算法——收敛速度的简单认识 引言收敛速度的判别标准 Q \mathcal Q Q-收敛速度 R \mathcal R R-收敛速度关于算法复杂度与收敛速度 引言 本节对收敛速度简单介绍。 收敛速度的判别标准 我们之前几节介绍了线搜索方法 ( Line Search Method ) (\text{Line …...
FPGA学习——Altera IP核调用之PLL篇
文章目录 一、IP核1.1 IP核简介1.2 FPGA中IP核的分类1.3 IP核的缺陷 二、PLL简介2.1 什么是PLL2.2 PLL结构图2.3 C4开发板上PLL的位置 三、IP核调用步骤四、编写测试代码五、总结 一、IP核 1.1 IP核简介 IP核(知识产权核),是在集成电路的可…...
经纬度坐标工具
LngLatUtil :用于计算里程数 import cn.hutool.core.util.ArrayUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.Getter; import lombok.Setter;import java.io.FileInputStream; import java.io.Serializable; import java.t…...
如何使用伪元素::before和::after?
伪元素(::before和::after)是CSS中非常有用的特性,它们允许你在元素的内容之前或之后插入额外的内容,并且不需要在HTML结构中添加额外的标记。这样可以方便地在页面上添加装饰性元素、图标、或者样式效果。以下是使用伪元素的基本方法: 1、创…...
Visual Studio Code中对打开的脚本格式统一
什么是Language Server Protocol (LSP)? Language Server Protocol(语言服务器协议,简称LSP)是微软在2016年提出的一套统一的通讯协议方案。LSP定义了一套编辑器或者IDE与语言服务器(Language Server)之间使用的协议&…...
补充JDK源码-IDEA集成工具
在阅读JDK8源码的时候发现,只有一小部分常用包是存在源码及其注释的,而很多内部包是没有源码,class文件在阅读的时候对阅读者十分不友好。在网上搜集了很多资料都没有解决问题。 解决问题办法:参考文档。本文主要是根据这篇文章记…...
Git Submodule 更新子库失败 fatal: Unable to fetch in submodule path
编辑本地目录 .git/config 文件 在 [submodule “Assets/CommonModule”] 项下 加入 fetch refs/heads/:refs/remotes/origin/...
Springboot切面打印日志
切面打印完整日志,以下代码用于扫描RestController 注解修饰的接口,并打印相关日志 import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; impor…...
ubuntu上回环设备/dev/loop0占用100%清理
查看磁盘占用情况时: df -h/dev/loopn这些设备在Linux下被称为回环设备。 终端输入: sudo apt autoremove --purge snapd再次查看:...
List list=new ArrayList()抛出的ArrayIndexOutOfBoundsException异常
1.应用场景,今天生产日志监控到一下ArrayList 进行add 异常,具体日志如下: eptionHandler.handler(178): TXXYBUSSINESS|执行异常 java.util.concurrent.CompletionException: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bo…...
桶排序算法
桶排序算法 算法思想概述:桶排序的主要步骤如下: 算法goland实现:图解演示: 算法思想概述: 桶排序(Bucket Sort)是一种非比较性的排序算法,它将待排序的元素分到有限数量的桶&#…...
P8604 [蓝桥杯 2013 国 C] 危险系数
题目背景 抗日战争时期,冀中平原的地道战曾发挥重要作用。 题目描述 地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。 我们来定义一个危险系数 DF(x,y)&…...
Excel·VBA表格横向、纵向相互转换
如图:对图中区域 A1:M6 横向表格,转换成区域 A1:C20 纵向表格,即 B:M 列转换成每2列一组按行写入,并删除空行。同理,反向操作就是纵向表格转换成横向表格 目录 横向转纵向实现方法1转换结果 实现方法2转换结果 纵向转横…...
Leetcode-每日一题【剑指 Offer 06. 从尾到头打印链表】
题目 输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。 示例 1: 输入:head [1,3,2]输出:[2,3,1] 限制: 0 < 链表长度 < 10000 解题思路 1.题目要求我们从尾到头反过…...
LeetCode--HOT100题(22)
目录 题目描述:160. 相交链表(简单)题目接口解题思路代码 PS: 题目描述:160. 相交链表(简单) 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表…...
产品体系架构202308版
1.前言 当我们不断向前奔跑时,需要回头压实走过的路。不断扩张的同时把相应的内容沉淀下来,为后续的发展铺垫基石。 不知从何时起,产品的架构就面向了微服务/中台化/前后端分离/低代码化/分布式/智能化/运行可观测化的综合体,让…...
Linux systemctl 简单介绍与使用
在Linux下,systemctl是一个管理系统服务的命令。它提供了对systemd服务的控制和管理。 在系统中使用systemctl命令,您可以执行以下操作: 启动服务:systemctl start servicename停止服务:systemctl stop servicename重…...
恺英网络宣布:与华为鸿蒙系统展开合作,将开发多款手游
8月5日消息,恺英网络宣布旗下子公司盛和网络参加了华为开发者大会(HDC.Together)游戏服务论坛,并在华为鸿蒙生态游戏先锋合作启动仪式上进行了亮相。恺英网络表示,将逐步在HarmonyOS上开发多款游戏,利用Har…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化
是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可,…...
鸿蒙HarmonyOS 5军旗小游戏实现指南
1. 项目概述 本军旗小游戏基于鸿蒙HarmonyOS 5开发,采用DevEco Studio实现,包含完整的游戏逻辑和UI界面。 2. 项目结构 /src/main/java/com/example/militarychess/├── MainAbilitySlice.java // 主界面├── GameView.java // 游戏核…...
flow_controllers
关键点: 流控制器类型: 同步(Sync):发布操作会阻塞,直到数据被确认发送。异步(Async):发布操作非阻塞,数据发送由后台线程处理。纯同步(PureSync…...
STM32标准库-ADC数模转换器
文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”:输入模块(GPIO、温度、V_REFINT)1.4.2 信号 “调度站”:多路开关1.4.3 信号 “加工厂”:ADC 转换器(规则组 注入…...
rm视觉学习1-自瞄部分
首先先感谢中南大学的开源,提供了很全面的思路,减少了很多基础性的开发研究 我看的阅读的是中南大学FYT战队开源视觉代码 链接:https://github.com/CSU-FYT-Vision/FYT2024_vision.git 1.框架: 代码框架结构:readme有…...
手动给中文分词和 直接用神经网络RNN做有什么区别
手动分词和基于神经网络(如 RNN)的自动分词在原理、实现方式和效果上有显著差异,以下是核心对比: 1. 实现原理对比 对比维度手动分词(规则 / 词典驱动)神经网络 RNN 分词(数据驱动)…...





