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

【数据结构与算法】指针美学与链表思维:单链表核心操作全实现与深度精讲
在这里插入图片描述
🔥小龙报:个人主页
🎬作者简介: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

从零开始:在本地搭建一个带知识库的 AI 助手(Ollama + Open WebUI)

从零开始:在本地搭建一个带知识库的 AI 助手(Ollama + Open WebUI)

一文讲清楚:要选哪些工具、需要什么环境、整体架构长什么样,以及一步步实现到能用的程度。 一、为什么要在本地搭一个 AI 助手? 过去一年,大模型从“新奇玩意儿”迅速变成“日常生产力工具”。但如果你只用网页版 ChatGPT / 文心一言 / 通义千问,会碰到几个很现实的问题: * 数据隐私:公司内部文档、个人笔记、聊天记录,你敢全部塞到线上吗? * 网络依赖:在飞机上、高铁里,或者公司内网严格管控时,在线 AI 直接“失联”。 * 额度与费用:免费额度有限,稍微重度一点就要付费,而且你也不知道自己的数据会不会被拿去训练。 本地部署一套 “AI + 知识库” 的好处就非常直观: 1. 数据完全不出本地,满足隐私合规要求。 2. 断网也能用,随时随地调取你的“第二大脑”。 3. 可定制:可以给团队搭一个“

By Ne0inhk

Web基础

万维网(Web)的四大核心构成 万维网的四大核心构成包括超文本传输协议(HTTP)、超文本标记语言(HTML)、统一资源定位符(URL)和超链接(Hyperlink)。这些技术共同支撑了现代互联网的基础架构。 超文本传输协议(HTTP) HTTP是客户端和服务器之间通信的基础协议,用于传输超文本数据。它定义了请求和响应的格式,支持GET、POST等方法来获取或提交资源。HTTP是无状态的,但通过Cookie等技术可以实现会话管理。 超文本标记语言(HTML) HTML是用于创建和设计网页的标准标记语言。它通过标签定义网页的结构和内容,如标题、段落、图像和链接。HTML5引入了更多语义化标签和多媒体支持,增强了网页的表现力和交互性。 统一资源定位符(URL) URL是用于标识互联网上资源位置的字符串。它由协议、域名、路径和查询参数等部分组成。例如,https://example.com/path?query=value。URL确保用户和程序能够准确访问特定的网络资源。 超链接(Hyperlink) 超链接是连接不同网页或资源的桥梁,通常以可点击的文本或图像形式呈现。它通

By Ne0inhk

一个 skill ,增加大模型前端的审美能力

上周,我让 AI 帮我做个落地页。 十分钟过去了,生成出来的东西—— 白色背景,紫色渐变,Inter 字体。 我直接关了。 你也遇到过吧? 用 AI 生前端,出来的东西都长一个样。 背景非白即黑,标题栏永远是紫色渐变,字体不是 Inter 就是 Roboto,配色永远是那套蓝绿红黄。 不是说不能用,但—— 太像 AI 了。 一眼看过去就是"机器生成",没有灵魂,没有个性。 直到昨天,我发现了一个东西。 Anthropic 官方出的一个 skill,叫 frontend-design。 让我再试一次。 这次不一样了 同样的提示词,同样的模型。 我只加了一句话: “使用 frontend-design skill” 结果呢?

By Ne0inhk
继续实践OpenClaw,好不容易把web 管理面板调通,再给它配上一个大模型

继续实践OpenClaw,好不容易把web 管理面板调通,再给它配上一个大模型

OpenClaw小龙虾是github 获得星标最多的项目,OpenClaw之所以能在GitHub上获得极高的关注度,主要原因在于它提供了一个功能强大、易于扩展的AI助手开发平台。把整个操作系统,打造成AI! OpenClaw官网:OpenClaw — Personal AI Assistant 以前的安装记录:https://skywalk.blog.ZEEKLOG.net/article/details/157554991 本来感觉OpenClaw安装是挺简单的,没想到巨坑,有一台机器装好后没有web管理面板.....所以本来很简短的文档,写成了巨幅文档。 安装OpenClaw 先在192.168.1.12安装,但是它没有systemd服务,导致OpenClaw的服务无法自动启动。需要手工执行openclaw gateway命令启动。 后在192.168.1.19安装。但是装好后没有web管理面板,反复删除重装也没有,最后是安装的openclaw-cn ,才解决了问题。参见这个文档:https://skywalk.blog.ZEEKLOG.net/article/

By Ne0inhk