【数据结构与算法】指针美学与链表思维:单链表核心操作全实现与深度精讲

【数据结构与算法】指针美学与链表思维:单链表核心操作全实现与深度精讲
在这里插入图片描述
🔥小龙报:个人主页
🎬作者简介:C++研发,嵌入式,机器人等方向学习者
❄️个人专栏:《C语言》《【初阶】数据结构与算法》
永远相信美好的事情即将发生
在这里插入图片描述

文章目录


前言

单链表是数据结构中最基础也最核心的线性表之一,熟练掌握其查找、指定位置插入与删除等操作,是深入学习算法与数据结构的关键一步。本文将从零实现单链表的常用接口,详细拆解每一步思路与代码细节,帮助大家真正理解指针操作与链表结构,夯实编程基础,为后续复杂结构打下扎实功底。


一、查找

思路: 遍历:pcur指向头结点,循环,当pucr不为空进入循环,pucr里面指向的数据为要查找的值的时候就返回pcur否则将pucr下一个结点的地址赋值给pcur然后继续判断,直到找到值。如果为空直接返回。

代码:

SLTNode*SLTFind(SLTNode* phead,SLTDataType x){ SLTNode* pcur = phead;while(pcur){if(pcur->data == x)return pcur; pcur = pcur->next;}return NULL;}

测试

//测试查找voidtest1(){ SLTNode* head = NULL;SLTPushFront(&head,1);SLTPushFront(&head,2);SLTPushFront(&head,3); SLTNode* find =SLTFind(head,1);if(find)printf("找到了!\n");elseprintf("未找到\n");}

运行结果:

在这里插入图片描述


时间复杂度:O(n)

二、指定位置之前或之后插入元素

2.1 在指定位置之前

思路: 头文件不能为空,也不能在空之前插入结点,首先要找到pos位置的前一个结点让它的next指针指向newnode,然后让newnode的next指针指向pos。如何找到pos的前一个结点?那就是遍历,从头结点开始,向后遍历,直到prev的next指针指向pos则就是pos的前一个结点。这里要注意,当pos为头结点的时候,执行的操作就变为了头插。

在这里插入图片描述

代码:

//在指定位置之前插入voidSLTInsert(SLTNode** pphead, SLTNode* pos,SLTDataType x){assert(pphead && pos);//只有一个节点if(pos ==*pphead)SLTPushFront(pphead, x);else{ SLTNode* newnode =SLTBuyNode(x); SLTNode* prev =*pphead;while(prev->next != pos) prev = prev->next; newnode->next = pos; prev->next = newnode;}}

测试

//测试在指定位置之前插入voidtest1(){ SLTNode* head = NULL;SLTPushBack(&head,1);SLTPushBack(&head,2);SLTPushBack(&head,3); SLTNode* find =SLTFind(head,2);if(find)SLTInsert(&head, find,7);SLTPrint(head);//打印}

运行结果:

在这里插入图片描述

时间复杂度:O(1)

2.2 在指定位置之后

思路: 当pos->next = newnode后,newnode->next = pos->next就变成了newnode->next = newnode,因为仅仅根据pos就能够找到下一个结点,不需要遍历,所以只用传两个数据就可,而且当pos为尾结点时插入数据,这个代码也没问题,不需要像pos在头结点前插入时一样重新调用一下头插函数

在这里插入图片描述

代码:

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

测试

//测试在指定位置之后插入voidtest1(){ SLTNode* head = NULL;SLTPushBack(&head,1);SLTPushBack(&head,2);SLTPushBack(&head,3); SLTNode* find =SLTFind(head,2);if(find)SLTInsertAfter(&head, find,7);SLTPrint(head);//打印}

运行结果:

在这里插入图片描述

时间复杂度:O(1)

三、指定位置删除或指定位置之后删除

3.1 在指定位置

思路: 这里还是通过遍历找到prev就是pos的前一个结点,然后让prev->next = pos->next,然后释放掉需要删除的那个结点
当pos为头结点的时候,通过遍历prev->next 并不能找不到pos,所以此时需要进行头删操作的引用

在这里插入图片描述

代码:

voidSLTErase(SLTNode** pphead, SLTNode* pos){assert(pphead && pos);//头结点if(pos ==*pphead)SLTPopFront(pphead);else{//寻找该位置前一个节点 SLTNode* prev =*pphead;while(prev->next != pos) prev = prev->next; prev->next = pos->next;free(pos); pos = NULL;}}

测试

//测试在指定位置删除voidtest1(){ SLTNode* head = NULL;SLTPushBack(&head,1);SLTPushBack(&head,2);SLTPushBack(&head,3); SLTNode* find =SLTFind(head,2);if(find)SLTErase(&head, find);SLTPrint(head);//打印}

运行结果:

在这里插入图片描述

时间复杂度:O(1)

3.2 指定位置之后

思路: 但是当pos为尾结点的时候,pos->next为NULL,所以del->next是对空指针解引用就会报错,所以在assert里面再加入一个条件assert(pos && pos->next)

在这里插入图片描述

代码:

//在指定位置之后删除voidSLTEraseAfter(SLTNode** pphead, SLTNode* pos){assert(pphead && pos); SLTNode* del = pos->next; pos->next = del->next;free(del); del = NULL;}

测试

//测试在指定位置之后删除
void test1()
{
SLTNode* head = NULL;
SLTPushBack(&head, 1);
SLTPushBack(&head, 2);
SLTPushBack(&head, 3);

SLTNode* find = SLTFind(head, 2); if (find) SLTEraseAfter(&head, find); SLTPrint(head); //打印 

}

运行结果:

在这里插入图片描述

时间复杂度:O(1)

四、代码展现

4.1 SList.h

#include <stdio.h>#include <stdlib.h> #include <assert.h> typedef int SLTDataType; typedef structSLTNode{SLTDataType data;//数值域 structSLTNode* next;//指针域外}SLTNode;voidSLTPrint(SLTNode* phead);//打印voidSLTDestory(SLTNode** pphead);//销毁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** pphead, SLTNode* pos,SLTDataType x);//在指定位置之后插入voidSLTErase(SLTNode** pphead, SLTNode* pos);//在指定位置删除voidSLTEraseAfter(SLTNode** pphead, SLTNode* pos);//在指定位置之后删除

4.2 SList.c

#define _CRT_SECURE_NO_WARNINGS 1#include "SList.h"//打印voidSLTPrint(SLTNode* phead){ SLTNode* pcur = phead;while(pcur){printf("%d->", pcur->data); pcur = pcur->next;}printf("NULL\n");}//节点申请 SLTNode*SLTBuyNode(SLTDataType x){ SLTNode* newnode =(SLTNode*)malloc(sizeof(SLTNode));if(newnode == NULL){printf("开辟失败!\n");exit(-1);} newnode->data = x; newnode->next = NULL;return newnode;}//销毁voidSLTDestory(SLTNode** pphead){ SLTNode* pcur =*pphead;while(pcur){ SLTNode* next = pcur->next;free(pcur); pcur = next;}*pphead = NULL;//手动置空,避免成为野指针}//尾插voidSLTPushBack(SLTNode** pphead,SLTDataType x){assert(pphead); SLTNode* newnode =SLTBuyNode(x);//申请节点if(*pphead == NULL)//链表为空,新节点为首节点*pphead = newnode;else{ SLTNode* pcur =*pphead;while(pcur->next != NULL)//寻找尾节点 pcur = pcur->next; pcur->next = newnode;}}//头插voidSLTPushFront(SLTNode** pphead,SLTDataType x){assert(pphead); SLTNode* newnode =SLTBuyNode(x); newnode->next =*pphead;*pphead = newnode;}//尾删voidSLTPopBack(SLTNode** pphead){//链表不能为空assert(pphead &&*pphead);//只有一个节点if((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{ SLTNode* prev = NULL;//尾节点的前一个节点 SLTNode* pcur =*pphead;while(pcur->next != NULL){ prev = pcur; pcur = pcur->next;}free(pcur); pcur = NULL; prev->next = 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;}return NULL;}//在指定位置之前插入voidSLTInsert(SLTNode** pphead, SLTNode* pos,SLTDataType x){assert(pphead && pos);//只有一个节点if(pos ==*pphead)SLTPushFront(pphead, x);else{ SLTNode* newnode =SLTBuyNode(x); SLTNode* prev =*pphead;while(prev->next != pos) prev = prev->next; newnode->next = pos; prev->next = newnode;}}//在指定位置之后插入voidSLTInsertAfter(SLTNode** pphead, SLTNode* pos,SLTDataType x){assert(pphead && pos); SLTNode* newnode =SLTBuyNode(x); newnode->next = pos->next; pos->next = newnode;}//在指定位置删除voidSLTErase(SLTNode** pphead, SLTNode* pos){assert(pphead && pos);//头结点if(pos ==*pphead)SLTPopFront(pphead);else{//寻找该位置前一个节点 SLTNode* prev =*pphead;while(prev->next != pos) prev = prev->next; prev->next = pos->next;free(pos); pos = NULL;}}//在指定位置之后删除voidSLTEraseAfter(SLTNode** pphead, SLTNode* pos){assert(pphead && pos); SLTNode* del = pos->next; pos->next = del->next;free(del); del = NULL;}

4.3 test.c

#include "SList.h"//测试尾插//void test1()//{// SLTNode* head = NULL;// SLTPushBack(&head, 1);// SLTPushBack(&head, 2);// SLTPushBack(&head, 3);// SLTPrint(head); //打印//}//测试头插//void test1()//{// SLTNode* head = NULL;// SLTPushFront(&head, 1);// SLTPushFront(&head, 2);// SLTPushFront(&head, 3);// SLTPrint(head); //打印//}//测试尾删//void test1()//{// SLTNode* head = NULL;// SLTPushBack(&head, 1);// SLTPushBack(&head, 2);// SLTPushBack(&head, 3); //// SLTPopBack(&head);// SLTPopBack(&head);// SLTPrint(head); //打印//}////测试头删//void test1()//{// SLTNode* head = NULL;// SLTPushFront(&head, 1);// SLTPushFront(&head, 2);// SLTPushFront(&head, 3);//// SLTPopFront(&head);// SLTPopFront(&head);// SLTPrint(head); //打印//}//测试查找//void test1()//{// SLTNode* head = NULL;// SLTPushFront(&head, 1);// SLTPushFront(&head, 2);// SLTPushFront(&head, 3);//// SLTNode* find = SLTFind(head, 1);// if (find)// printf("找到了!\n");// else// printf("未找到\n");//}//测试在指定位置之后插入//void test1()//{// SLTNode* head = NULL;// SLTPushBack(&head, 1);// SLTPushBack(&head, 2);// SLTPushBack(&head, 3); //// SLTNode* find = SLTFind(head, 2);// if (find)// SLTInsertAfter(&head, find, 7);// SLTPrint(head); //打印//}////测试在指定位置删除//void test1()//{// SLTNode* head = NULL;// SLTPushBack(&head, 1);// SLTPushBack(&head, 2);// SLTPushBack(&head, 3); //// SLTNode* find = SLTFind(head, 2);// if (find)// SLTErase(&head, find);// SLTPrint(head); //打印//}////测试在指定位置之后删除//void test1()//{// SLTNode* head = NULL;// SLTPushBack(&head, 1);// SLTPushBack(&head, 2);// SLTPushBack(&head, 3);//// SLTNode* find = SLTFind(head, 2);// if (find)// SLTEraseAfter(&head, find);// SLTPrint(head); //打印//}intmain(){test1();return0;}

五、顺序表和链表的区别

在这里插入图片描述


注:缓存利用率参考存储体系结构 以及 局部原理性。

总结与每日励志

✨据结构的学习没有捷径,每一个指针、每一次遍历、每一段接口实现,都是夯实功底的必经之路。保持耐心,多写多练多思考,把基础打牢,复杂的算法与项目自然水到渠成。永远相信,坚持与专注,终将让你在编程路上稳步前行、闪闪发光。

在这里插入图片描述

Read more

Docker配置镜像源 解决拉取镜像超时 ERROR: failed to solve xx: : failed to resolve source metadata for docker.io/li

Docker配置镜像源 解决拉取镜像超时 ERROR: failed to solve xx: : failed to resolve source metadata for docker.io/li

分析问题: 我在构建镜像时报错:ERROR: failed to solve: golang:1.21: failed to resolve source metadata for docker.io/library/golang:1.21: failed to do request: Head "https://registry-1.docker.io/v2/library/golang/manifests/1.21": dial tcp 202.160.128.205:443: i/o timeout 排查原因,是构建过程中拉取镜像时出现了超时问题。

By Ne0inhk
十五、中秋特别篇:深入理解 SELinux - 系统安全的“月饼”与“门禁”

十五、中秋特别篇:深入理解 SELinux - 系统安全的“月饼”与“门禁”

值此中秋佳节,皓月当空,阖家团圆。在享受美味月饼的同时,我们不妨来聊聊Linux 系统中那位默默守护“家宅平安”的忠诚卫士——SELinux。 SELinux (Security-Enhanced Linux) 是一个由美国国家安全局 (NSA)主导开发的强制访问控制 (MAC)安全子系统。它不再仅仅依赖“钥匙”,而是像一个一丝不苟的智能门禁系统。在这个系统里,每个人(进程) 和每个房间(文件、端口)都被贴上了独一无二的电子标签 (安全上下文)。门禁系统手握一本厚厚的“通行规则手册” (安全策略),严格规定了“谁”在什么时间可以进入“哪个房间”做什么事。 思维导图 一、SELinux 的核心概念与工作流程 想象一下中秋家宴,SELinux就像家里的管家,确保一切井然有序。 核心概念中秋家宴比喻描述示例主体家庭成员/客人发起动作的实体,通常是进程。httpd 进程 (想端菜的厨师)客体月饼/

By Ne0inhk
基于ApachePOI实现高德POI分类快速导入PostgreSQL数据库实战

基于ApachePOI实现高德POI分类快速导入PostgreSQL数据库实战

目录 前言 一、高德POI分类简介 1、数据表格 2、分类结构 二、从Excel导入到Postgresql 1、Excel解析流程 2、Mybatis批量导入 3、数据入库 三、总结 前言         在大数据与地理信息深度交融的当下,地理信息系统(GIS)的触角已延伸至各个领域,为行业决策提供精准的空间数据支撑。而 POI(Point of Interest,兴趣点)分类数据,作为 GIS 数据的细分瑰宝,正发挥着独特且关键的作用。高德地图所涵盖的丰富 POI 分类数据,如美食、电影院、酒店、购物场所等,宛如一座有待深度挖掘的宝藏,对商业选址、城市规划、旅游服务等行业有着不可估量的价值。         以商业领域为例,掌握准确的美食分类 POI 数据,

By Ne0inhk
ClawPanel — 开源 OpenClaw 智能管理面板,20+ 通道接入 / 多模型配置 / Docker 一键部署

ClawPanel — 开源 OpenClaw 智能管理面板,20+ 通道接入 / 多模型配置 / Docker 一键部署

🐾 一个比官方控制台更强大的 OpenClaw 可视化管理工具,支持 QQ、微信、Telegram、Discord 等 20+ 通道统一管理,多 AI 模型提供商配置,技能中心,版本管理,环境检测,Docker 一键部署。 📌 项目简介 ClawPanel 是一个基于 React + TypeScript + Express 的 OpenClaw 智能管理面板,旨在为 OpenClaw 用户提供一个比官方控制台更强大、更直观的可视化管理工具。 项目前身是 openclaw-im-manager(一个简单的 QQ 机器人管理后台),经过 4 个大版本迭代,现已进化为功能完整的 OpenClaw 全能管理面板。 GitHub 地址:https://github.com/zhaoxinyi02/ClawPanel

By Ne0inhk