数据结构-单链表

数据结构-单链表

单链表

概念与结构

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

链表就类似一个火车,车头是哨兵位(可有可无),车厢是节点

在这里插入图片描述
  • 将火车里的某节车厢去掉或加上,不会影响其他车厢,每节车厢都是独立存在的。

在链表⾥,每节“车厢”是什么样的呢? \color{red}{在链表⾥,每节“车厢”是什么样的呢?} 在链表⾥,每节“车厢”是什么样的呢?

在这里插入图片描述

结点

与顺序表不同的是,链表⾥的每节"车厢"都是独立申请下来的空间,我们称之为“结点/结点结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量链表中每个结点都是独立申请的(即需要插入数据时才去申请一块结点的空间),我们需要通过指针变量来保存下一个结点位置才能从当前结点找到下一个结点。

链表的性质

1、链式机构在逻辑上是连续的,在物理结构上不一定连续
2、结点⼀般是从堆上申请的
3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续

结合前⾯学到的结构体知识,我们可以给出每个结点对应的结构体代码:
假设当前保存的结点为整型:

structSListNode{int data;//结点数据structSListNode* next;//指针变量⽤保存下⼀个结点的地址};

当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数据,也需要保存 \color{green}{当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数据,也需要保存} 当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数据,也需要保存
下⼀个结点的地址(当下⼀个结点为空时保存的地址为空) \color{green}{下⼀个结点的地址(当下⼀个结点为空时保存的地址为空)} 下⼀个结点的地址(当下⼀个结点为空时保存的地址为空)
当我们想要从第⼀个结点走到最后⼀个结点时,只需要在当前结点拿上下⼀个结点的地址就可以了。 \color{green}{当我们想要从第⼀个结点走到最后⼀个结点时,只需要在当前结点拿上下⼀个结点的地址就可以了。} 当我们想要从第⼀个结点走到最后⼀个结点时,只需要在当前结点拿上下⼀个结点的地址就可以了。

链表的打印

在这里插入图片描述

实现单链表

头文件

  • SList.h
#include<stdio.h>#include<stdlib.h>#include<assert.h>//链表的结构typedefint SLTDataType;typedefstructSListNode{ SLTDataType data;structSListNode* next;//指向下一个节点的地址}SLTNode;//typedef struct SListNode SLTNode;voidSLTPrint(SLTNode* phead);//尾插voidSLTPushBack(SLTNode** pphead, SLTDataType x);//头插voidSLTPushFront(SLTNode** pphead, SLTDataType x);//尾删voidSLTPopBack(SLTNode** pphead);//头删voidSLTPopFront(SLTNode** pphead);//查找 SLTNode*SLTFind(SLTNode* phead, SLTDataType x);//在指定位置之前插⼊数据voidSLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//在指定位置之后插入数据voidSLTInsertAfter(SLTNode* pos, SLTDataType x);//删除pos结点voidSLTErase(SLTNode** pphead, SLTNode* pos);//删除pos之后的结点voidSLTEraseAfter(SLTNode* pos);//销毁链表voidSListDestroy(SLTNode** pphead);

源文件

  • SList.c

单链表的打印

voidSLTPrint(SLTNode * phead){ SLTNode* pcur = phead;while(pcur !=NULL){printf("%d -> ", pcur->data); pcur = pcur->next;}printf("NULL\n");}

单链表申请新节点内存

SLTNode*SLTBuyNode(SLTDataType x){ SLTNode* newnode =(SLTNode*)malloc(sizeof(SLTNode));if(newnode ==NULL){perror("malloc fail!");exit(1);} newnode->data = x; newnode->next =NULL;return newnode;}
这里是申请新节点,并且把对应的数据存入节点中,所以接下来只需要将各个节点连接起来(链表中的指针)

尾插

//尾插voidSLTPushBack(SLTNode** pphead, SLTDataType x){assert(pphead);//申请新节点 SLTNode* newnode =SLTBuyNode(x);//链表为空if(*pphead ==NULL){*pphead = newnode;}else{ SLTNode* ptail =*pphead;while(ptail->next !=NULL){ ptail = ptail->next;}//找到了尾结点 ptail newnode ptail->next = newnode;}}
传址调用,SLTNode** pphead,不然无法修改SLTNode* phead结构体指针需要做出特殊判断,当链表为空的时候

头插

//头插voidSLTPushFront(SLTNode** pphead, SLTDataType x){assert(pphead); SLTNode* newnode =SLTBuyNode(x);//newnode *pphead newnode->next =*pphead;*pphead = newnode;}

尾删

//尾删voidSLTPopBack(SLTNode** pphead){//链表为空不能删除assert(pphead &&*pphead);//链表只有一个节点的情况if((*pphead)->next ==NULL){free(*pphead);*pphead =NULL;}else{ SLTNode* prev =NULL; SLTNode* ptail =*pphead;while(ptail->next){ prev = ptail; ptail = ptail->next;}//prev ptail prev->next =NULL;free(ptail); ptail =NULL;}}
处理特殊情况,链表只有一个节点的情况链表为空不能删除

头删

//头删voidSLTPopFront(SLTNode** pphead){assert(pphead &&*pphead); SLTNode* next =(*pphead)->next;free(*pphead);*pphead = next;}

查找

//查找 SLTNode*SLTFind(SLTNode* phead, SLTDataType x){ SLTNode* pcur = phead;while(pcur){if(pcur->data == x){return pcur;} pcur = pcur->next;}//未找到returnNULL;}
不需要传二级指针了

在指定位置之前插入数据

//在指定位置之前插⼊数据voidSLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x){assert(pphead && pos); SLTNode* newnode =SLTBuyNode(x);//pos指向头结点if(pos ==*pphead){//头插SLTPushFront(pphead, x);}else{//找pos的前一个节点 SLTNode* prev =*pphead;while(prev->next != pos){ prev = prev->next;}//prev newnode pos prev->next = newnode; newnode->next = pos;}}
pos不能为空指针特殊情况,pos指向头结点

在指定位置之后插入数据

//在指定位置之后插入数据voidSLTInsertAfter(SLTNode* pos, SLTDataType x){assert(pos); SLTNode* newnode =SLTBuyNode(x);//pos newnode pos->next newnode->next = pos->next; pos->next = newnode;}

删除pos结点

//删除pos结点voidSLTErase(SLTNode** pphead, SLTNode* pos){assert(pphead && pos);//pos刚好就是头结点——头删if(pos ==*pphead){SLTPopFront(pphead);}else{ SLTNode* prev =*pphead;while(prev->next != pos){ prev = prev->next;}//prev pos pos->next prev->next = pos->next;free(pos); pos =NULL;}}
pos不能为空指针特殊情况,pos刚好就是头结点—头删

删除pos之后的结点

//删除pos之后的结点voidSLTEraseAfter(SLTNode* pos){assert(pos && pos->next);//pos del del->next SLTNode* del = pos->next; pos->next = del->next;free(del); del =NULL;}

销毁链表

//销毁链表voidSListDestroy(SLTNode** pphead){assert(pphead); SLTNode* pcur =*pphead;while(pcur){ SLTNode* next = pcur->next;free(pcur); pcur = next;}*pphead =NULL;}

链表的分类

链表的结构⾮常多样,以下情况组合起来就有 8 种( 2 x 2 x 2 )链表结构 \color{green}{链表的结构⾮常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构} 链表的结构⾮常多样,以下情况组合起来就有8种(2x2x2)链表结构

在这里插入图片描述
  • 链表说明:
在这里插入图片描述
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:单链表和双向带头循环链表
1. 无头单向非循环链表:结构简单,⼀般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,⼀般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了

代码地址

仓库

感谢大佬们三连,你的鼓励就是对我创作的激励!!! \color{purple}{感谢大佬们三连,你的鼓励就是对我创作的激励!!!} 感谢大佬们三连,你的鼓励就是对我创作的激励!!!

在这里插入图片描述

Read more

OpenClaw相关的开源AI项目汇总大全:本文涵盖近期所有OpenClaw相关的GitHub高星star热门项目

OpenClaw相关的开源AI项目汇总大全:本文涵盖近期所有OpenClaw相关的GitHub高星star热门项目

OpenClaw相关的开源AI项目汇总大全:本文涵盖近期所有OpenClaw相关的GitHub高星star热门项目 💡 导读 GitHub上这些OpenClaw开源项目,Star数为什么能破千?我们扒了13个宝藏仓库后发现… 有人用OpenClaw给钉钉搭了智能助手,有人在飞书里养了个AI女友Clawra,还有人把记忆层memU玩成了第二大脑——而这些全部免费开源! 2026年OpenClaw热度飙升,但官方文档晦涩、部署门槛高劝退无数人?别慌!本文汇总了OpenClawInstaller、OneClaw、Moltworker等13个硬核开源项目,覆盖:✅ 一键部署工具(零代码上手)✅ 钉钉/企微/飞书/微信全平台接入方案✅ 云端托管+本地Sandbox双模式✅ 记忆层memU、技能库Skills、甚至AI女友Clawra… 收藏这一篇,省掉你100个小时的踩坑时间! 文章目录 * OpenClaw相关的开源AI项目汇总大全:本文涵盖近期所有OpenClaw相关的GitHub高星star热门项目 * 💡 导读 * 一、OpenClawInstall

By Ne0inhk
【Linux】从版本控制到代码调试:Git 入门与 GDB 调试器学习指南

【Linux】从版本控制到代码调试:Git 入门与 GDB 调试器学习指南

前言  作为 Linux 开发的 “左膀右臂”,Git 管版本、gdb 调程序 —— 前者搞定代码的迭代与协同,后者专治程序里的各种 “疑难杂症”。这篇博客就从 Git 的核心概念、基础操作,讲到 gdb 的调试指令,把这俩工具的高频用法讲透,帮你把开发效率直接拉满。 目录 一、Git 版本控制器 1.1 什么是 Git? 【问题】:什么是分布式? 【问题】:什么是集中式? 1.2 Git 核心概念  1.3 GitHub 与 Gitee 二、Git 基础操作 1. Git安装 2. 远程仓库与本地仓库联动(以

By Ne0inhk
地瓜机器人智慧医疗——贰贰玖想要分享的关于使用惯导的一些思路

地瓜机器人智慧医疗——贰贰玖想要分享的关于使用惯导的一些思路

前言 在第20届全国大学生智能车竞赛(智慧医疗机器人创意赛)中,我们贰贰玖拿下国一。在这里,作为队长兼技术主力兼机师兼……我想分享一下在备赛过程中的一些思路。当然,为了不把比赛搞成全都是20s以内,竞争激烈到前后几名差0.几秒,我不会开源我们的惯导和避障思路(实在太简单,太容易实现了)。 这是我们两年的备赛日记,也有我们第二年区域赛和国赛的全流程。 【贰贰玖|从省三到国一,从巡线到路径规划到惯导+纯视觉避障的贰贰玖智能车日记-哔哩哔哩】 https://b23.tv/IDJyM2P 数据集我放在这里了,一共2w9张,全都是640x480,有数据增强的(没有旋转):https://pan.baidu.com/s/10u4S4fiVATRyEeDpdzpk_A?pwd=0229 提取码:0229 下面面我会讲一下我们的网络问题怎么解决,上位机的一些辅助处理,如何半场扫码,如何准确返回 P 点,修改stm32,以及修改车的ekf.yaml。

By Ne0inhk
HarmonyOS6 底部导航栏组件 rc_concave_tabbar 使用指南

HarmonyOS6 底部导航栏组件 rc_concave_tabbar 使用指南

文章目录 * 前言 * 组件特性 * 适用场景 * 使用说明 * 安装组件 * 安装步骤 * 步骤一:引入相关依赖 * 步骤二:创建菜单数据 * 步骤三:使用导航组件 * 运行效果 * 参数介绍 * TabsConcaveCircle 组件参数 * TabMenusInterfaceIRequired 菜单项配置 * 进阶使用 * 自定义单个菜单项颜色 * 调整动画速度 * 自定义高度和颜色 * 注意事项 * 总结 前言 rc_concave_tabbar 是一个功能强大、样式精美的 HarmonyOS 底部导航栏组件库,提供凹陷圆形动画效果样式,适用于多种场景。本篇将介绍 rc_concave_tabbar 的使用方法以及其相关的设计理念。 组件特性 * 流畅动画:支持流畅的凹陷圆形切换动画效果 * 高度定制:支持自定义背景色、字体颜色、高度等多种样式配置 * 灵活配置:支持全局配置和单项配置,满足不同场景需求

By Ne0inhk