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

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

备战蓝桥杯----C/C++组 (一)所需C++基础知识(上)

备战蓝桥杯----C/C++组 (一)所需C++基础知识(上)

个人主页: wengqidaifeng ✨永远在路上,永远向前走 个人专栏: 数据结构 C语言 嵌入式小白启动! 重要OJ算法题详解 文章目录 * 前言 * 一. 分析大纲,了解所需 * 1. 大纲显示内容 * 2、组别划分与难度关系 * 3、知识点结构分析(按组别) * 3.1 大学C组:基础入门阶段 * 3.2 大学B组:中级提高阶段 * 3.3 大学A组 / 研究生组:高级挑战阶段 * 4.难度系数说明 * 二. C++基础语法(上):从零开始的编程基石 * 1.前言 * 2.开发环境搭建 - DevC++的安装与使用 * 2.1

By Ne0inhk
【C++】 map/multimap底层原理与逻辑详解

【C++】 map/multimap底层原理与逻辑详解

【C++】 map/multimap底层原理与逻辑详解 * 摘要 * 目录 * 一、`map` * 1. 类模板认识 * 2. 构造函数认识 * 3. 迭代器和范围for的使用 * 4. insert的使用 * 5. empty 和size的使用 * 6. erase的使用 * 7. swap 和 clear的使用 * 8. find的使用 * 9. count的使用 * 11. lower_bound 和 upper_bound的使用 * 12. equal_range的使用 * 13. operator= 的使用 * 14. operator[ ] 的使用 * 二、`multimap` * 1. 模板和类模板的认识 * 2. insert的使用 * 3.

By Ne0inhk
【Spring AI & LangChain4j 进阶篇】Milvus/Redis向量库整合Java:RAG核心环节手把手代码实现

【Spring AI & LangChain4j 进阶篇】Milvus/Redis向量库整合Java:RAG核心环节手把手代码实现

🍃 予枫:个人主页 📚 个人专栏: 《Java 从入门到起飞》《读研码农的干货日常》 💻 Debug 这个世界,Return 更好的自己! 引言 大模型幻觉是落地路上的“绊脚石”,而RAG(检索增强生成)正是破解这一痛点的核心方案。作为Java开发者,如何依托Spring AI、LangChain4j等生态,实现从文档解析、向量存储到检索生成的全链路落地?本文拆解RAG核心逻辑,手把手教你用Java搭建可直接复用的RAG系统,覆盖多格式文档处理、主流向量库整合与全流程优化,干货拉满,建议收藏! 文章目录 * 引言 * 一、RAG核心认知:为什么它能解决大模型幻觉? * 1.1 RAG是什么?核心逻辑拆解 🧩 * 1.2 RAG适用场景与全链路流程 * 1.2.1 核心适用场景 * 1.2.2 RAG全链路核心流程(Java视角) * 二、

By Ne0inhk
飞算JavaAI:智能ai工具箱,普通程序员也能高效交付

飞算JavaAI:智能ai工具箱,普通程序员也能高效交付

做程序员这些年,最烦的就是接到需求模糊、涉及模块多的项目——就像这次要开发的本地生活食材配送平台,需求只写了“支持商家入驻、用户下单、食材溯源、配送调度、售后维权”,具体怎么拆分功能、设计架构,完全没头绪。换以前,光和产品经理对接需求、画架构图就要3天,加上编码、调试,没两周根本交不出活。但用了飞算JavaAI专业版后,整个开发周期压缩到一周,还提前2天完成测试上线,核心靠的就是它的智能引导系统和实用AI工具,让复杂项目开发变得有条理、高效率。 智能引导五步法:从模糊需求到可落地代码,一步不卡壳 面对食材配送平台的多元需求,我一开始只知道核心方向,具体的功能模块、接口设计、数据存储完全是一团乱麻。飞算JavaAI的智能引导系统,就像一个贴身助手,一步步帮我把需求拆解、落地,全程没遇到卡壳的地方。 1. 第一步理解需求仅用 15 秒就拆解出 12 个关键功能点,涵盖商家、用户、商品、订单、配送等基础模块,还补充了食材溯源、

By Ne0inhk