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

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

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

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

链表的类型

单链表

上面的就是单链表

双链表

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

循环链表

在这里插入图片描述
单链表的最后一个结点存的是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.mzph.cn/bicheng/27404.shtml

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

相关文章

Java优雅统计耗时【工具类】

任务耗时如何优雅的打印,看完本文你就明白了!~ import cn.hutool.core.date.StopWatch; import cn.hutool.core.lang.Console;/*** 优雅打印出任务耗时*/ public class Main {public static void main(String[] args) throws Exception{StopWatch stopW…

macOS Sequoia 开发者测试版下载和安装教程

macOS Sequoia 于 2024年6月10日在WWDC 2024 上发布,里面添加了AI、窗口排列、操控iPhone等功能,目前发布的为测试版本,可能很多人不知道怎么去下载安装,现在小编教一下大家怎么安装最新的 macOS Sequoia 开发者测试版。 下载 mac…

2024 年最新使用 Node 搭建QQ开放平台官方 QQ 频道机器人详细教程(更新中)

注册 QQ 开放平台账号 QQ 开放平台是腾讯应用综合开放类平台,包含 QQ 机器人、QQ 小程序、QQ 小游戏 等集成化管理,也就是说你注册了QQ 开放平台,你开发 QQ 机器人还是 QQ 小程序都是在这个平台进行部署上线和管理。 如何注册 QQ 开放平台账…

JAVAEE值之网络原理(1)_用户数据报协议(UDP)、概念、特点、结构、代码实例

前言 在前两节中我们介绍了UDP数据报套接字编程,但是并没有对UDP进行详细介绍,本节中我们将会详细介绍传输层中的UDP协议。 一、什么是UDP? UDP工作在传输层,用于程序之间传输数据的。数据一般包含:文件类型&#xff0…

超图制作栅格数据集专题图示例

之前写过一两篇专题图的博文,是制作的矢量数据集的专题图; 有一个栅格数据集如下,不知是干嘛的,可能是一个地形,或水系; 看一下对栅格数据集制作专题图;能制作的专题图类型少些, 先…

R 文件优化插件:Binary XML file in layout Error inflating class

场景一:构造函数缺失 问题 自定义布局(FlagmentLayout)加载自定义属性失败,导致广告显示异常,甚至是闪退~ InflateException 在 Android 中我们遇到的通常发生在自定义 View 创建中,动态加载…

探索交互设计:五大关键维度全面剖析

交互式设计是用户体验(UX)设计的重要组成部分。在本文中,我将向大家解释什么是交互设计并简要描述交互设计师通常每天都做什么。 一、什么是交互设计 交互式设计用简单的术语来理解就是用户和产品之间的交互。在大多数情况下,当…

大白菜PE系统进入时一直 ACPI_BIOS_ERROR

安装系统PE不支持,主板不兼容,换个WIN10的PE就解决了,跟之前部分电脑需要WIN8的PE同理 WIN10PE教程 WIN8PE教程

CLIPSeg

作者回答问题敷衍,不建议复现

谷歌Google广告开户要提供什么材料?

谷歌Google广告是企业出海,触及全球潜在客户的必备渠道,无论您是初创公司还是成熟企业,想要在激烈的市场竞争中脱颖而出,有效利用谷歌广告的力量至关重要。云衔科技,作为数字化营销解决方案与SaaS软件服务商&#xff0…

【Tkinter界面】Canvas 图形绘制(02/5)

文章目录 一、说明二、几何时使用 Canvas 组件2.1 用法2.2 简单范例2.3 对象移动2.4 对象删除2.5 文字对象显示 三、画布和画布对象3.1 画布生成函数原型3.2 使用create_xxx()方法3.3 对参数**options的解释 一、说明 Canvas(画布)组件为 Tkinter 的图形…

多类型图像OCR:基于Dify的多模态Agent实现

大模型相关目录 大模型,包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步,扬帆起航。 大模型应用向开发路径:AI代理工作流大模型应用开发实用开源项目汇总大模…

记录一下PHP使用微信小程序支付

记录一下PHP使用微信小程序支付V3版本经历 官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_0.shtml 请详细查看文档中小程序支付接入前准备(https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml&#xff…

仿element-ui 实现自己组件库 <3>

目录 input 组件封装 v-model用在组件上 显示和隐藏密码 封装switch组件 实现转换的功能 设置checkbox input 组件封装 首先input组件的基本框架和样式&#xff1a; <div class"miao-input"><input class"miao-input_inner" > </div…

网络编程(四)

一、使用wireshark抓包分析协议头 &#xff08;一&#xff09;wireshark常用的过滤语句 tcp.port <想要查看的端口号> ip.src <想要查看的源IP地址> ip.dest <想要查看的目的IP地址> ip.addr <想要查看的IP地址>&#xff08;二&#xff09;抓包分…

Burp Suite Professional 2024.5 (macOS, Linux, Windows) - Web 应用安全、测试和扫描

Burp Suite Professional 2024.5 (macOS, Linux, Windows) - Web 应用安全、测试和扫描 Burp Suite Professional, Test, find, and exploit vulnerabilities. 请访问原文链接&#xff1a;Burp Suite Professional 2024.5 (macOS, Linux, Windows) - Web 应用安全、测试和扫描…

IP服务器代理如何设置使用?

IP服务器代理&#xff08;通常称为代理IP或代理服务器&#xff09;的设置和使用方法可以根据不同的需求和场景而有所不同。以下是一个清晰的步骤指南&#xff0c;帮助你设置和使用IP服务器代理&#xff1a; 1. 选择合适的代理IP类型 根据使用目的的不同&#xff0c;可以选择不…

如何将ai集成到项目中,方法二

上一篇文章&#xff1a;如何将ai集成到radsystems项目中&#xff0c;在项目中引入ai-CSDN博客 上一篇文章内容主要针对于未实现权限分离的项目&#xff0c;这篇文章主要来说一下权限分离的项目怎么做&#xff0c;以及注意的细节。 一、编写前端router.js 二、编写前端askai.vu…

基础-02-数据通信基础

文章目录 1.信道特征1.1 数据通信概念1.2 信道特性-信道带宽W1.3 信道特性-码元和码元速率1.4 信道特性-奈奎斯特定理1.5 信道特性-香农定理1.6 带宽/码元速率/数据速率关系梳理1.7 练习题 2.信道延迟2.1 信道延迟概念2.2 信道延迟计算2.3 练习题 3. 传输介质3.1 传输介质概念3…

4/8路 HDD/SSD 1080 车载NVR,高清车载录像机(8路1080P硬盘机

4/8路 HDD/SSD 1080 车载NVR 产品主要特点&#xff1a; -支持4/8路实时高清数字 1080P录像 -硬盘记录数据&#xff08;最大支持2TB&#xff09; -支持GPS全球定位, 可选模块 -支持WIFI高速自动下载功能, 可选模块 -内置3/4G模块&#xff0c;实时预览和远程管理&#xff0c…