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

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

Python 爬虫实战:爬取酷狗音乐热门歌曲榜单(附完整源码)

前言 酷狗音乐作为国内主流的音乐平台之一,其热门歌曲榜单汇聚了当下最受用户欢迎的音乐作品,包含歌曲名称、歌手、播放量、评分等丰富信息。掌握酷狗音乐热门榜单的爬取方法,既能帮助音乐爱好者整理心仪的歌曲列表,也能为音乐数据分析提供基础数据源。本文将详细讲解如何使用 Python 爬取酷狗音乐热门歌曲榜单数据,涵盖接口分析、数据请求、JSON 解析、数据存储等核心环节,代码规范可直接运行,适合爬虫初学者系统学习。 摘要 本文以酷狗音乐 TOP500 热门榜单页面(https://www.kugou.com/yy/rank/home/1-8888.html)为爬取目标,通过分析酷狗音乐榜单的 API 接口,使用requests库发送 HTTP 请求获取 JSON 格式的榜单数据,提取歌曲排名、名称、歌手、播放量、时长、评分等核心信息,并将数据存储为 CSV

By Ne0inhk
python之路并不一马平川:带你踩坑Pandas

python之路并不一马平川:带你踩坑Pandas

这是我的亲身经历。作为一名全能型的混子,Pandas是我吃饭的家伙之一,但光是把它请到我的电脑上,就差点让我“饭碗不保”。这是一段长达数周,充满挫折、困惑和最终解脱的曲折历程。我将带你完整回顾我踩过的每一个坑,以及那最后的“救命稻草”。我将以第一视角,带你完整回顾我踩过的那些坑,以及我是如何一步步爬出来的。 记得刚入行那年,我接手的第一个项目是个电商小程序开发。当时为了赶进度,我直接跳过了需求分析阶段,结果上线后发现支付接口和后台数据对不上,不得不紧急下架整改。那三天三夜不眠不休的debug经历,现在想起来还心有余悸。 去年在开发智能家居App时,我又犯了个典型错误:没有做好版本兼容性测试。当用户反馈老型号设备无法连接时,我们才发现蓝牙协议栈对新老设备的处理方式完全不同。这个教训让我养成了建立完整测试矩阵的习惯。 最惨痛的经历是去年年底的云服务迁移。当时为了节省成本,我选择了直接全量迁移数据库,结果因为网络波动导致数据不一致,差点酿成重大事故。现在我做数据迁移时都会严格遵循"全量备份-增量同步-数据校验"的标准流程。 这些血泪教训让我明白,在技术这条路上,捷径往往是最远的路。每

By Ne0inhk
Python + BS4实战:手把手带你爬取商业数据

Python + BS4实战:手把手带你爬取商业数据

目录 一、bs4篇 1.bs4介绍 1.1 什么是BeautifulSoup4? 1.2 为什么选择BeautifulSoup4?       核心优势 2.bs4详解 2.1 首先下载bs4 2.2 接下来引入一个使用bs4的例子让我们快速熟悉它 2.3 运行结果 3.bs4使用实战案例 3.1 完整代码 3.2 为什么会影响翻页 3.3 反爬机制 3.4 已知信息 3.5 解决思路 3.6 结果展示 3.7 容易混淆的一点 3.8 图片爬虫 🌟 Hello,

By Ne0inhk

Qwen3-VL SDK发布:支持Python/Java/C#多语言调用

Qwen3-VL SDK发布:支持Python/Java/C#多语言调用 在智能应用日益依赖“看懂图像、理解语言”的今天,开发者面临一个现实难题:如何让AI真正理解一张截图里的错误提示,并像人类一样给出修复建议?过去这需要组合OCR、目标检测、自然语言模型等多个系统,工程复杂度极高。而现在,随着Qwen3-VL SDK的正式发布,这一切变得像调用一个函数那样简单。 这款新推出的软件开发工具包,首次将通义千问系列最强大的视觉-语言模型以标准化接口形式开放给Python、Java和C#开发者。它不再只是“能识别图片的文字”,而是可以分析界面布局、生成网页代码、执行GUI操作、甚至理解长达数小时的视频内容——所有这些能力,都可以通过几行代码接入现有系统。 多模态智能的进化:从感知到行动 传统视觉-语言模型大多停留在“描述性理解”阶段:输入一张图,输出一段文字说明。但真实世界的应用需求远不止于此。用户希望的是——看到表单就知道怎么填,看到报错就能自动修复,读完文档可以直接生成PPT。这就要求模型不仅“看得懂”,还要“会做事”。 Qwen3-VL正是朝着这个方向迈出的关键一步。

By Ne0inhk