【数据结构初阶】 --- 单链表

关于链表你应该先了解这些

下图描述了物理模型和逻辑模型,大多数常见的其实是逻辑模型,但这对初学者或者掌握不扎实的同学不太友好,所以这里我重点讲解物理模型,当了解了这些细节,以后做题或是什么就直接画逻辑模型就好了
在这里插入图片描述
物理模型:
在这里,一个大方框表示着一个结点,这个结点里存储了两种数据:

  • 一是你本身要存储的数据
  • 二是为了让这些结点具有连接作用,而需要存放相应结点的地址,这样,当你访问了当前的结点内容,想要访问下一个结点的内容,这时就需要有下一个节点的地址,这就是为什么要存地址的原因。

链表的类型

单链表

上面的就是单链表

双链表

在这里插入图片描述
与单链表的不同就是一个结点里存有两个地址,可以实现双向访问,弥补了单链表只能单向访问的缺点

循环链表

在这里插入图片描述
单链表的最后一个结点存的是NULL,而循环链表的最后一个结点存放的是第一个结点的地址,事实上,你看这张图,确实也分辨不出哪个是第一个结点,所以想象成单链表最后一个结点放的头结点的地址就行

链表的存储方式

数组是一块连续的内存,而链表不同,每个结点都有自己的地址,并没有联系,这些地址是取决于操作系统的内存管理(在没学习操作系统之前可以当做这些地址是随机分配的)

链表的定义

这一节,我所讲的知识都是基于单链表

typedef int SLTDataType;//结点中存储数据的类型
typedef struct SListNode
{SLTDataType data;//结点中要存储的数据struct SListNode* next;//结点中指向下一个结点的指针
}SLTNode;

先看这张图:
在这里插入图片描述
与开头的那张区别就在于头指针phead,这是一个指针变量,用来存放第一个结点的地址,利用该指针就可以依次访问或操作节点中的数据
接下来初始化一个指向结点的头指针:(这里是头指针,不是头结点)

SLTNode* phead = NULL;

链表的操作

先了解是如何访问链表的

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
完整流程:
在这里插入图片描述

为什么要用二级指针?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

经过了头插操作,我们将new_node成功连入链表的头部,pphead存的也是new_node的地址,但是,我们的实参phead,它里面的内容竟还是之前第一个结点的内容,而往后我们还是要用phead来访问操作这个链表但新插入的节点却永远访问不到,那你说能不能用pphead访问不就行了,记住,pphead是局部变量,执行完头插这个函数pphead就不存在了,我们想操作这个链表,一直都是利用phead当做实参传递的。想要改变实参的内容只需传址调用即可,因此,我们将指针变量phead的地址当做实参传递过去,那么相应的,形参就需要一个二级指针接收,因为phead是指针,我们传递的是指针的地址,所以需要二级指针接收。如此,通过对二级指针pphead解引用就可以直接对phead的内容进行修改,到此,phead就可以存new_node地址了

创建一个新节点

SLTNode* SLTCreatNode(SLTDataType x)
{
//里用malloc函数向栈区开辟一个大小为sizeof(SLTNode)个字节的空间,将这个空间的首地址存放于new_p这个指针变量中SLTNode* new_p = (SLTNode*)malloc(sizeof(SLTNode));malloc函数开辟空间失败会返回NULL,这时就不要再进行后续操作,避免解引用空指针if (new_p == NULL){perror("malloc");//进行报错,在屏幕上会出现错误提示exit(0);//直接让程序结束}//开辟成功就将x存入新的节点中,节点中的指针next指向NULLnew_p->data = x;new_p->next = NULL;return new_p;
}

打印链表

这里只需将头指针的内容作为实参穿过来,利用形参指针phead接收就能达到效果

void SLTPrint(SLTNode* phead)//这里接收的是第一个结点的地址
{while (phead != NULL){printf("%d->", phead->data);phead = phead->next;}printf("NULL\n");
}

头插法

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead != NULL);SLTNode* p_node = SLTCreatNode(x);p_node->next = *pphead;*pphead = p_node;
}

尾插法

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{//pphead里存的是phead的地址,不可能为NULL,所以如果传来NULL就会出问题,因此需要防止误传NULL指针,这里就要断言一下assert(pphead != NULL);SLTNode* p_node = SLTCreatNode(x);//试着将空链表的逻辑带入else中,你会发现cur->next在访问空指针,这是不行的,所以要专门为链表为空这个特殊情况写一段代码if (*pphead == NULL){*pphead = p_node;}else{SLTNode* cur = *pphead;while (cur->next != NULL){cur = cur->next;}cur->next = p_node;}
}

头删法

void SLTPopFront(SLTNode** pphead)
{assert(pphead != NULL);assert(*pphead != NULL);//如果传来的是个空链表就报错SLTNode* tmp = *pphead;*pphead = (*pphead)->next;free(tmp);
}

尾删法

//尾删的时候分三种情况讨论,没有节点,一个节点,多个节点
//不是说上来你就知道要分三种情况,而是当你写出适应多个节点的代码后
//这时你就要考虑边界情况,没有节点或者一个还是两个节点的情况
void SLTPopBack(SLTNode** pphead)
{assert(pphead != NULL);assert(*pphead != NULL);//头指针为空(没有节点)就报错if ((*pphead)->next == NULL)//只有一个节点的情况{free(*pphead);*pphead = NULL;}else//多个节点{SLTNode* cur = *pphead;SLTNode* prev = NULL;while (cur->next != NULL){prev = cur;cur = cur->next;}prev->next = NULL;free(cur);}
}

给指定的地址插入数据

这里是要将数据插入到pos前,为什么我们会知道一个结点的地址pos呢,答案就是查找函数帮你找的

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead != NULL);assert(pos != NULL);assert(*pphead);//传过来头指针指向的是空SLTNode* p_new = SLTCreatNode(x);if (*pphead == pos){p_new->next = *pphead;*pphead = p_new;}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}p_new->next = prev->next;prev->next = p_new;

查找数据

返回的是查找到结点的地址

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

销毁链表(意外的知识)

在C语言和C++中程序员在堆区开辟的数据需要程序员亲手释放,一个一个开出的节点,只能一个一个释放掉。
这里既可以用一级指针也可以用二级指针,不过有个小小的区别,当你用free释放一个指针的动态内存后需要及时置空,那么在这里,我们的头指针phead在释放完链表后也需要及时置空
但是,

  • 如果你传的实参是phead本身,那当SLTDestory执行完后,你需要额外的将phead手动置空
  • 如果传的是phead的地址,那么在函数SLTDestory中,释放完链表后,就可以操作*pphead = NULL,其实就等于在函数内部将函数外部的phead置空,出了函数,就不需要手动置空了
  • 到这里或许是有些抽象,你可以想一下free函数,它把指针释放后,需要你手动置空,实际上原因就是你传的是指针本身,free函数不能在它的内部将这个指针置空,所以需要你在free执行完手动置空
void SLTDestory(SLTNode* phead)
{SLTNode* cur = phead;while (cur != NULL){SLTNode* nxt = cur->next;free(cur);cur = nxt;}
}
void test()
{SLTNode* phead = NULL;...SLTDestory(phead);phead = NULL;//手动置空
}
void SLTDestory(SLTNode** pphead)//用二级指针接收phead的地址
{SLTNode* cur = *pphead;while(cur != NULL){SLTNode* nxt = cur->next;free(cur);cur = nxt;}*phead = NULL;//远程操作置空
}
void test()
{LSTNode* phead = NULL;...SLTDestory(&phead);//实参是指针变量phead的地址
}

哨兵位

在这里插入图片描述

  • 哨兵位也算是一个结点,但哨兵位是不计入链表的长度。
  • 哨兵位里不需要存值,最好不要存,有的数据结构书上会将哨兵位存入链表的结点个数,但这里的前提是你创建的链表是存储int型的数据,如果是char呢,那么根据数据在内存中的存储方式不同,char型只能存储8个bit的数据,范围也就是-128~127,如果这个链表结点个数大于127,那么这个哨兵位存的数据就会出错,如果换做是浮点型,那就更离谱了,所以哨兵位最好不要存储数据。

初始化

LSTNode* phead = (LSTNode*)malloc(sizeof(LSTNode));
//phead这个指针变量指向哨兵位

传参

有了哨兵位,再也不用为二级指针苦恼了
每当我们可能对链表的头结点进行插入或者删除(实际是phead指向的结点的地址改变了,phead的内容需要更改,只能通过二级指针的方式远程操控phead)
现在哨兵位出现了,你想对链表的头结点进行操作,想改变头结点的地址,随便改,我phead现在指向是哨兵位,哨兵位的地址是固定不变的,再也不用担心要修改链表的同时还要考虑需不需要修改phead的指向,当哨兵位不改变时,链表怎样的增删改,都只会影响哨兵位的指向,与phead无关

这里用头删演示:

void SLTPopFront(SLTNode* phead)
{assert(phead != NULL);assert(phead->next != NULL);//当链表为空还要删就报错SLTNode* tail = phead->next;phead->next = tail->next;free(tail);
}

为什么都是用头删,头插演示呢,

  • 那是因为这两个操作更改的是第一个结点,节点发生改变,那就要更新指向这个结点的指针,现在的函数实参传的是哨兵位的地址,phead指向的也就是哨兵位,传的是哨兵位的地址那就可以对哨兵位进行更新,而远在函数外的头指针,一直指向的都是固定不变的哨兵位的地址。
  • 而前面我们如果更改了第一个结点,那就要更新头指针,想在函数里更改函数外的指针只能传指针的地址,指针的地址就需要二级指针接收。
  • 当然,重点讲带头单链表的原因也在于算法题里的链表大部分都是带头链表,避免与哨兵位混淆。哨兵位在某些时刻也会帮忙简化一些判断甚至帮助解题,后续有机会讲算法题会提到的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/347204.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

经典文献阅读之--FlashOcc(快速且内存高效的占用预测模块)

Tip: 如果你在进行深度学习、自动驾驶、模型推理、微调或AI绘画出图等任务,并且需要GPU资源,可以考虑使用UCloud云计算旗下的Compshare的GPU算力云平台。他们提供高性价比的4090 GPU,按时收费每卡2.6元,月卡只需要1.7元每小时&…

基础IO(下)

基础IO 1. 磁盘1.1 磁盘的物理结构1.2 磁盘的逻辑抽象结构 2. 理解文件系统2.1 前言2.2 文件系统2.3 文件的新建和删除2.4 文件的查找2.5 理解软硬链接 3. 动态库和静态库3.1 生成静态库3.2 生成动态库3.3 动态库加载 实际上,大部分文件都不是被打开的(当…

Catia装配体零件复制

先选中要复制的零件 然后选中复制到的父节点才可以。 否则 另外一种方法是多实例化

Jmeter07:函数

1 Jmeter组件:函数 1.1 是什么? 是程序中的封装单元(最小的),封装一些功能实现 1.2 为什么? 优点1:易读 易维护 优点2:实现功能复用 1.3 怎么用? 流程: 1&…

Linux RS232

一、确认硬件信息 RS232: 引脚信息: 二、软件配置 1、pinctrl信息: 2、设备树节点: 3、修改串口支持的模式 三、驱动 bsp/drivers/uart/sunxi-uart.c 四、烧录测试 查看串口参数: stty -F /dev/ttyAS3 -a stty -F…

解锁ChatGPT:从原理探索到GPT-2的中文实践及性能优化

⭐️我叫忆_恒心,一名喜欢书写博客的研究生👨‍🎓。 如果觉得本文能帮到您,麻烦点个赞👍呗! 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧,喜欢的小伙伴给个三连支…

面向对象设计原则概述

面向对象设计原则概述 面向对象设计原则概述 面向对象设计原则概述单一职责原则开闭原则里氏代换原则依赖倒转原则接口隔离原则合成复用原则迪米特法则 内容来自《设计模式与艺术》一文。后续会陆续分享书中值得深思观点。 面向对象设计的目标之一在于支持可维护性复用&#xf…

Nginx配置详细解释:(5)rewrite重写功能

rewrite重写功能,在编译安装时需要有相应的模块,ngx_http_rewritte_module模块指令中,有if指令,return,set,break等指令。 1.ngx_http_rewrite_module模块指令 1.if指令 if指令在nginx配置中,用于条件判断&#xff…

Huggingface-cli 登录最新版(2024)

安装Huggingface-cli pip install -U "huggingface_hub[cli]"设置好git的邮箱和用户名和huggingface的github账号一致 git config --global user.mail xxx git config --global user.name xxx登录 复制token,划红线的地方,在命令行中点击右…

迷宫最短路径求解--c++

【代码】 #include<iostream> #include<queue> #include<stack> using namespace std; #define ROW 8 #define COL 8 //测试迷宫数据 int maze[ROW][COL] {{0,0,0,1,0,0,0,0},{0,1,0,1,0,1,0,1},{0,1,0,0,0,1,0,1},{0,1,0,1,1,1,0,1},{0,1,0,1,1,0,0,0},{0…

Fiddler抓包工具详细使用教程

各位做测试的同学想必对抓包工具fiddler并不陌生&#xff0c;但是很多同学可能没有总结过它的用法&#xff0c;下面我总结了fiddler一些常用的用法。 Web端抓包配置 打开Fiddler&#xff0c;Tools -> Fiddler Options -> HTTPS 配置完后记得要重启Fiddler 选中Decrpt …

34.打印K型

上海市计算机学会竞赛平台 | YACSYACS 是由上海市计算机学会于2019年发起的活动,旨在激发青少年对学习人工智能与算法设计的热情与兴趣,提升青少年科学素养,引导青少年投身创新发现和科研实践活动。https://www.iai.sh.cn/problem/76 题目描述 小爱想用 * 打出一个大写的 K。…

6.11 作业

以下是一个简单的比喻&#xff0c;将多态概念与生活中的实际情况相联系&#xff1a; 比喻&#xff1a;动物园的讲解员和动物表演 想象一下你去了一家动物园&#xff0c;看到了许多不同种类的动物&#xff0c;如狮子、大象、猴子等。现在&#xff0c;动物园里有一位讲解员&…

使用fvm切换flutter版本

切换flutter版本 下载fvm 1、dart pub global activate fvm dart下载fvm 2、warning中获取下载本地的地址 3、添加用户变量path&#xff1a; 下载地址 终端查看fvm版本 fvm --version 4、指定fvm文件缓存地址 fvm config --cache-path C:\src\fvm&#xff08;自定义地址&…

《精通ChatGPT:从入门到大师的Prompt指南》附录A:常用Prompt示例

附录A&#xff1a;常用Prompt示例 在《精通ChatGPT&#xff1a;从入门到大师的Prompt指南》的附录A中&#xff0c;我们将展示一系列常用的Prompt示例&#xff0c;帮助读者更好地理解和应用Prompt技术。每个示例将包含Prompt的描述、使用场景、预期结果以及实际输出。希望这些示…

调试环境搭建(Redis 6.X 版本)

今儿&#xff0c;我们来搭建一个 Redis 调试环境&#xff0c;目标是&#xff1a; 启动 Redis Server &#xff0c;成功断点调试 Server 的启动过程。使用 redis-cli 启动一个 Client 连接上 Server&#xff0c;并使用 get key 指令&#xff0c;发起一次 key 的读取。 视频可见…

公司面试题总结(二)

7. 说说 JavaScript 中的数据类型&#xff1f;存储上的差别&#xff1f; • 基本类型&#xff1a; o Number o String o Boolean o Undefined o null o symbol • 引用类型 o Object o Array o Function • 声明变量时不同的内存地址分配&#xff1a; o 简单类型的…

Excel最基本的常用函数

最基本最常用的函数&#xff0c;掌握了可以解决大部分问题。 (笔记模板由python脚本于2024年06月11日 19:05:56创建&#xff0c;本篇笔记适合熟悉excel的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣…

小白学RAG:大模型 RAG 技术实践总结

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 汇总合集…

【设计模式】行为型设计模式之 策略模式学习实践

介绍 策略模式&#xff08;Strategy&#xff09;&#xff0c;就是⼀个问题有多种解决⽅案&#xff0c;选择其中的⼀种使⽤&#xff0c;这种情况下我们 使⽤策略模式来实现灵活地选择&#xff0c;也能够⽅便地增加新的解决⽅案。⽐如做数学题&#xff0c;⼀个问题的 解法可能有…