【数据结构入坑指南(三.1)】--《面试必看:单链表与顺序表之争,读懂“不连续”之美背后的算法思想》

【数据结构入坑指南(三.1)】--《面试必看:单链表与顺序表之争,读懂“不连续”之美背后的算法思想》

🔥@晨非辰Tong:个人主页 

👀专栏:《C语言》《数据结构与算法》

💪学习阶段:C语言、数据结构与算法初学者

⏳“人理解迭代,神理解递归。”


引言:学完顺序表,深受其扩容和插入删除低效之苦?

是时候认识单链表了。它用指针串联数据,以“不连续”的物理结构,完美解决了顺序表的痛点。

接下来,让我们一起探索这种更优雅的数据结构。

目录

一、单链表特性全解

1.1  单链表:数据的"一线牵"

1.2  单链表结构:数据的“小火车”

二、单链表的实现(初)

2.1  铺垫

2.2  单链表尾插

2.3  单链表头插

2.4  单链表尾删


一、单链表特性全解

1.1  单链表:数据的"一线牵"

--在学习顺序表时,发现有几个缺陷:

  • 中间/头部的插入、删除,时间复杂度为O(N);
  • 增容要申请新空间,拷贝数据,释放旧空间,有消耗;
  • 增容一般呈两倍增长,会空间浪费;

--由于对上面的思考(改善),引入单链表:

--链表:链表是一种是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序通过链表的指针链接次序实现。

1.2  单链表结构:数据的“小火车”

--链表的逻辑结构是线性的,可想象成一列火车拉着一节一节的车厢:

--对于这种结构,每个车厢都是独立空间,且将车厢称为节点,由两部分组成:存储的数据;下一节点的地址;

-- plist 保存第一个节点的地址(指向第一个节点),通过修改 plist 保存的内容可以实现指向不同的节点。

--注意:需要插入数据时才会申请一块节点大小的空间

--而在堆空间申请空间,节点不一定时整齐排放(物理结构不一定连续),但每个节点都存者下一节点的地址,就通过这种方式进行连接。


二、单链表的实现(初)

2.1  铺垫

--先简单实现以下单链表的打印:

SList.h #pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h> //链表基本结构_节点结构 typedef int SLTDataType; typedef struct SListNode { SLTDataType data;//存数据 struct SListNode* next;//存储下一节点地址 }SLTNode;//重命名_节点 //或者——> typedef struct SListNode SLTNode; ——————————————————————————————————————————————————————————————————————————————————— //声明_打印函数 void SLTPrint(SLTNode* phead);//phead指向第一个节点,后续通过地址调换节点
SList.c #include "SList.h" //定义_打印函数 void SLTPrint(SLTNode* phead) { SLTNode* pcur = phead;//临时赋值 //循环打印 while (pcur != NULL) { printf("%d -> ", pcur->data);//输出数据 pcur = pcur->next;//指向下一节点 } printf("NULL\n"); } //对于循环条件,pcur存储首节点地址,当经过循环内的地址跳转,最终指向NULL结束 ————————————————————————————————————————————————————————————————————————————————— test.c int test01() { //创建链表—>节点之间的链接 SLTNode* node1 = (SLTNode*) malloc(sizeof(SLTNode)); SLTNode* node2 = (SLTNode*) malloc(sizeof(SLTNode)); SLTNode* node3 = (SLTNode*) malloc(sizeof(SLTNode)); SLTNode* node4 = (SLTNode*) malloc(sizeof(SLTNode)); //初始化_赋值链表 //存储数据 node1->data = 1; node2->data = 2; node3->data = 3; node4->data = 4; //链接地址 node1->next = node2;//node存储的是地址 node2->next = node3; node3->next = node4; node4->next = NULL; //打印链表 SLTNode* phead = node1; SLTPrint(phead); } int main() { test01(); return 0; }

2.2  单链表尾插

--在后续进行的头插等都需要申请新的节点空间,先封装函数:

SList.c //定义_申请节点 SLTNode* SLTBuyNode(SLTDataType x) { //申请一个节点大小的空间 SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); if (newnode == NULL) { perror("malloc fail!"); exit(1); } newnode->data = x; newnode->next = NULL; return newnode; }

--实现尾插

SList.c //定义_尾插 void SLTPushBack(SLTNode** pphead, SLTDataType x)//应用时传地址,二级指针接收 { //申请新节点空间->插入数据 SLTNode* newnode = SLTBuyNode(x);//node->存储着新空间的地址 //链表一开始为空 //pphead->二级指针变量—>存储一级指针变量的地址 if (*pphead == NULL)//解引用(phead本身)判断链表是否为空 { *pphead = newnode;//测试端的指针(phead)指向新空间 } //链表不为空 else { //遍历找到尾节点(空地址),插入新节点 SLTNode* ptail = *pphead;//防止*pphead指向发生变化 while (ptail->next != NULL) { ptail = ptail->next;//向后遍历 } //循环外找到地址为空节点->插入新节点 ptail->next = newnode; } } 
首先判断为空,将申请节点作首节点;非空,遍历找到尾节点—>next=NULL,将newnode给next,完成地址链接。

--注意:尾插函数参数是二级指针,进行传址,接收指针(plist)地址。
test.c void test02() { //创建空链表 SLTNode* phead = NULL;//首节点为空 //尾插 SLTPushBack(&phead, 1);//为了使形参的变化影响实参,传地址 SLTPushBack(&phead, 2); SLTPushBack(&phead, 3); SLTPushBack(&phead, 4); SLTPrint(phead); } int main() { test02(); return 0; }

2.3  单链表头插

SList.c //定义_头插 void SLTPushFront(SLTNode** pphead, SLTDataType x) { assert(pphead); //申请节点空间 SLTNode* newnode = SLTBuyNode(x); //*pphead 相当于 phead(存储首节点地址,指向首节点) newnode->next = *pphead;//新节点存储首节点得地址 *pphead = newnode;//指向首节点指针改变指向对象 }
--头插的实现很简单,因为头指针*pphead相当于phead(始终指向首节点),先将新节点存上首节点地址,将改变首节点的指向(phead),就完成了插入。
test.c void test02() { SLTNode* phead = NULL;//空链表 SLTPushFront(&phead, 1); SLTPrint(phead); SLTPushFront(&phead, 2); SLTPrint(phead); SLTPushFront(&phead, 3); SLTPrint(phead); SLTPushFront(&phead, 4); SLTPrint(phead); } int main() { test02(); return 0; }

2.4  单链表尾删

SList.c //定义_尾删 void SLTPopBack(SLTNode** pphead) { //链表为空不能删除 assert(pphead && *pphead);//pphead为空—>解引用错误;*pphead为空->首节点为空(链表为空),无法删除 //链表只有一个节点,直接删除 if ((*pphead)->next == NULL) { free(*pphead); *pphead = NULL; } else { SLTNode* prev = NULL; SLTNode* ptail = *pphead; while (ptail->next) { prev = ptail; ptail = ptail->next; } //prev ptail prev->next = NULL; free(ptail); ptail = NULL; } } 
--prev 指针始终指向 ptail 前一个节点,当 ptail -> next 指向 NULL然后释放,prev指向的节点为尾节点,此时将prev->next置为NULL,完成尾删。

--特殊的是,链表只有一个节点,就要特殊处理。不需要 prev 的指向作用,直接将 ptail 释放置空。
test.c void test02() { //创建空链表 SLTNode* phead = NULL;//phead头指针,指向的首节点为空,代表链表没有节点 //尾插 SLTPushBack(&phead, 1);//为了使形参的变化影响实参,传地址 SLTPushBack(&phead, 2); SLTPushBack(&phead, 3); SLTPushBack(&phead, 4); SLTPrint(phead); //尾删 SLTPopBack(&phead); SLTPrint(phead); SLTPopBack(&phead); SLTPrint(phead); SLTPopBack(&phead); SLTPrint(phead); SLTPopBack(&phead); SLTPrint(phead); } int main() { test02(); return 0; }

回顾:

《数据结构与算法精讲:从数组到顺序表,如何让数据管理变得强大而优雅?》​​

《从数组到动态顺序表:数据结构与算法如何优化内存管理?》
结语:至此,单链表的基础骨架已然搭建。我们实现了增删,也领略了其“一线牵”的灵活。

然而,这趟旅程远未结束。单链表还有更多形态等待探索:如何更高效地查找?环状链表有何妙用?双链表又如何实现双向奔赴?

掌握基础,方能构筑大厦。下一站,我们将继续深入,解锁单链表的更多潜能。

Read more

Flutter 三方库 flutter_adaptive_scaffold 的鸿蒙化适配指南 - 掌握一套代码适配全场景终端的自适应架构技术、助力鸿蒙应用构建从手机到平板及折叠屏的极致无缝交互体系

Flutter 三方库 flutter_adaptive_scaffold 的鸿蒙化适配指南 - 掌握一套代码适配全场景终端的自适应架构技术、助力鸿蒙应用构建从手机到平板及折叠屏的极致无缝交互体系

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 flutter_adaptive_scaffold 的鸿蒙化适配指南 - 掌握一套代码适配全场景终端的自适应架构技术、助力鸿蒙应用构建从手机到平板及折叠屏的极致无缝交互体系 前言 在 OpenHarmony 鸿蒙应用追求“万物互联、全场景覆盖”的伟大进程中,屏幕尺寸的多样性(从 6 英寸手机到 12 英寸平板,再到 2D/3D 模式切换的折叠屏)是每一位 UI 开发者必须正面迎接的挑战。如何在不为每种设备重写 UI 的前提下,实现导航栏自动从“底部”平滑流转到“侧边”?如何在宽屏模式下自动开启“双栏(Master-Detail)”布局?flutter_adaptive_scaffold 作为一个由 Flutter

By Ne0inhk
Flutter 三方库 system_shortcuts 的鸿蒙化适配指南 - 实现快速触发系统级快捷功能、支持 WiFi 开关、亮度调节与系统设置一键直达

Flutter 三方库 system_shortcuts 的鸿蒙化适配指南 - 实现快速触发系统级快捷功能、支持 WiFi 开关、亮度调节与系统设置一键直达

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 system_shortcuts 的鸿蒙化适配指南 - 实现快速触发系统级快捷功能、支持 WiFi 开关、亮度调节与系统设置一键直达 前言 在进行 Flutter for OpenHarmony 的应用工具开发时,能够快速引导用户跳转到系统设置页面,或直接触发某些系统级快捷功能(如切换静音、调节亮度)是提升交互效率的关键。system_shortcuts 是一个封装了各平台快捷路径的库。本文将探讨如何在鸿蒙系统下利用该库构建极致便捷的系统级操作流。 一、原理解析 / 概念介绍 1.1 基础原理 system_shortcuts 核心是通过平台通道(MethodChannel)调用操作系统的 want(鸿蒙的启动意图)或特定的系统服务接口。它屏蔽了复杂的跳转 URI 拼接,提供了语义化的接口。 封装

By Ne0inhk
云开发 Copilot ——让开发变得更简单

云开发 Copilot ——让开发变得更简单

声明:本篇博客为云开发 Copilot体验文章,非广告 目录 前言: 游客体验 云开发 Copilot实战: 一、图片生成需求 二、云开发 Copilot实现需求 三、AI生成低代码页面 Copilot 的亮点功能 使用场景 云开发 Copilot开发的前景展望 前言: 在云开发AI+中,腾讯云提供一系列与 AI 相关的功能,如大模型接入、 Agent 等,帮助开发者为自己的小程序、web 或者应用快速接入 AI 能力,同时也提供了云开发 Copilot,来加速用户的开发,帮助用户更快构建自己的应用。下面博主将会为大家实战使用云开发 Copilot来助力开发。 云开发 Copilot是云开发推出的一款 AI 开发辅助工具,可以帮助用户快速生成多种类型的应用功能,包括低代码应用、页面、组件、数据模型、

By Ne0inhk
Flutter 三方库 appstream 的鸿蒙化适配指南 - 驾驭 Linux 生态元数据规范,打造高性能、标准化、国际化的 OpenHarmony 桌面应用商店分发基石

Flutter 三方库 appstream 的鸿蒙化适配指南 - 驾驭 Linux 生态元数据规范,打造高性能、标准化、国际化的 OpenHarmony 桌面应用商店分发基石

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 appstream 的鸿蒙化适配指南 - 驾驭 Linux 生态元数据规范,打造高性能、标准化、国际化的 OpenHarmony 桌面应用商店分发基石 前言 随着鸿蒙(OpenHarmony)生态向 PC 和平板端的高速扩张,如何为海量的三方软件建立一套标准化的“数字档案”,成了构建应用商店生态的核心痛点。过去,开发者提交应用信息时,往往采用碎片化的 JSON 或自定义文档。这会导致软件分发时详情页展示不一、多语言支持混乱,甚至连基本的截图和版本日志都难以对齐。 为了解决这个问题,我们需要引入一套具备全球化视野的元数据定义标准。appstream 作为 Linux 生态下最重要的应用信息描述规范,能够通过结构化的 XML 标签,精准定义软件的身世、功能和展示资产。适配到鸿蒙平台后,它不仅能让你的重型“鸿蒙私有应用商店”瞬间具备吞金般的解析能力,

By Ne0inhk