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

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

欢迎来到 s a y − f a l l 的文章 欢迎来到say-fall的文章 欢迎来到say−fall的文章 🌈say-fall:个人主页🚀专栏:《手把手教你学会C++》 | 《C语言从零开始到精通》 | 《数据结构与算法》 | 《小游戏与项目》💪格言:做好你自己,才能吸引更多人,与他们共赢,这才是最好的成长方式。 前言: 关于上一篇文章的多态原理他来啦! 在上一篇《多态核心:虚函数、override、final、纯虚函数总结》中,我们已经初步认识了 C++ 多态的语法层面:虚函数、重写、纯虚函数等关键知识点,并提到了多态的底层依赖于 vptr 虚指针 与 vtable 虚函数表。但很多同学在学习时,仍然会有这些疑问: * 为什么带虚函数的类,sizeof 大小会多出

By Ne0inhk
C++ 运算符重载:自定义类型的运算扩展

C++ 运算符重载:自定义类型的运算扩展

C++ 运算符重载:自定义类型的运算扩展 💡 学习目标:掌握运算符重载的核心语法与规则,能够为自定义类型重载常用运算符,实现类对象的灵活运算。 💡 学习重点:运算符重载的基本形式、成员函数与全局函数重载的区别、常见运算符的重载实现、禁止重载的运算符。 一、运算符重载的概念与核心价值 ✅ 结论:运算符重载是 C++ 静态多态的重要体现,允许为自定义类型(如类、结构体)重新定义运算符的行为,让自定义对象可以像内置类型一样使用运算符。 运算符重载的核心价值: 1. 简化代码书写:用直观的运算符替代繁琐的成员函数调用,提升代码可读性 2. 统一操作风格:让自定义类型的运算逻辑与内置类型保持一致,降低学习和使用成本 3. 扩展类型功能:根据业务需求定制运算符的行为,满足自定义类型的运算需求 ⚠️ 注意事项:运算符重载不会改变运算符的优先级和结合性,也不会改变运算符的操作数个数。 二、运算符重载的基本语法 运算符重载的本质是函数重载,分为成员函数重载和全局函数重载两种形式。 2.1 成员函数重载语法 将运算符重载函数定义为类的成员函数,语法格式如下: class

By Ne0inhk
C++学习之旅【C++伸展树介绍以及红黑树的实现】

C++学习之旅【C++伸展树介绍以及红黑树的实现】

🔥承渊政道:个人主页 ❄️个人专栏: 《C语言基础语法知识》《数据结构与算法》 《C++知识内容》《Linux系统知识》 ✨逆境不吐心中苦,顺境不忘来时路!🎬 博主简介: 引言:前篇文章,小编已经介绍了关于C++AVL树的实现!相信大家应该有所收获!接下来我将带领大家继续深入学习C++的相关内容!本篇文章着重介绍关于C++伸展树介绍以及红黑树的实现!伸展树与红黑树是两类极具代表性的BBST,且在工程实践中各有不可替代的价值:伸展树摒弃了"严格平衡”的执念,通过“伸展”操作将最近访问的节点移至根节点,利用“局部性原理”优化频繁访问的场景,实现均摊O(logn)的时间复杂度,适合缓存、热点数据查询等场景;红黑树则通过给节点着色并遵守严格的颜色规则,确保树的最长路径不超过最短路径的两倍,以 “弱平衡” 换稳定的最坏O(logn)性能,是C++ STL 中 std::map、std:

By Ne0inhk
【C++】C++中内存管理的利器“智能指针”

【C++】C++中内存管理的利器“智能指针”

各位大佬好,我是落羽!一个坚持不断学习进步的学生。 如果您觉得我的文章还不错,欢迎多多互三分享交流,一起学习进步! 也欢迎关注我的blog主页:落羽的落羽 文章目录 * 一、智能指针的场景需求 * 二、智能指针的设计思路 * 三、C++标准库中的智能指针 * 1. auto_ptr * 2. unique_ptr * 3. shared_ptr * 4. weak_ptr * 5. 特殊说明 * 四、智能指针的实现原理 * 五、shared_ptr的循环引用问题与weak_ptr的使用 一、智能指针的场景需求 如果一个程序中手动new了对象,申请了空间资源,然后下面抛出了异常,就会导致申请的资源没有手动释放,造成内存泄露了。我们就需要在捕捉到异常后在catch语句里先delete资源。可是,new本身也可能抛异常的,导致我们处理起来就会很麻烦。智能指针在这样的场景下处理就十分轻松了。 doubledivide(int

By Ne0inhk