链表基础知识(一、单链表)
一、链表表示和实现
顺序表的问题及思考
问题:
1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到
200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:
如何解决以上问题呢?下面给出了链表的结构来看看。
1.1 链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,用于存储逻辑关系为 “一对一” 的数据。链表中每个数据的存储都由以下两部分组成:
1.数据元素本身,其所在的区域称为数据域。
2.指向直接后继元素的指针,所在的区域称为指针域。
(单链表的节点)
数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
二、链表的分类:
2.1实际中要实现的链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向、双向
2. 带头、不带头
3. 循环、非循环
-
头节点:是一个节点,本质上是一个结构体变量,区分数据域和指针域,不存放任何数据,只存放指向链表中真正存放数据的第一个节点的地址,仅用于辅助管理整个链表的结构。
-
头指针:是一个指针,本质上是一个结构体类型的指针变量,不区分数据域和指针域,它仅存储链表中第一个节点的地址。
-
带头节点也就意味着不需要传二级指针了
1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带
来很多优势,实现反而简单了,后面我们代码实现了就知道了。
2.2链表和顺序表的对比
三、单链表
3.1单链表的优劣:
1、查找速度慢
2、 不能从后往前
3、找不到前驱
4、单向链表不能自我删除,需要靠辅助节点 。
无头+单向+非循环链表增删查改实现
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
3.2SList.h
#pragma once //防止重复包含
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>typedef int SLTDataType;//方便改变数据类型
struct SListNode
{SLTDataType data;struct SListNode* next;//下一个地址//结构体中嵌套指针,但不能嵌套自己,会死循环
}SLTNode;
typedef struct SListNode STLNode;// 不会改变链表的头指针,传一级指针
void SListPrint(STLNode* phead);// 可能会改变链表的头指针,传二级指针
void SListPushBack(STLNode** pphead, SLTDataType x);
void SListPushFront(STLNode** pphead, SLTDataType x);//分有节点头插和无节点头插,尾插得分开处理
void SListPopFront(STLNode** pphead);
void SListPopBack(STLNode** pphead);// 不会改变链表的头指针,传一级指针
STLNode* SListFind(STLNode* pphead, SLTDataType x);
// 在pos的前面插入x
void SListInsert(STLNode** pphead, STLNode* pos, SLTDataType x);
// 删除pos位置的值
void SListErase(STLNode** pphead, STLNode* pos);有些地方也有这样定义在pos的前面插入x
//void SListInsert(STLNode** phead, int i, SLTDataType x);删除pos位置的值
//void SListErase(STLNode** phead, int i);
3.3打印链表
第一步:输出第一个节点的数据域,输出完毕后,让指针保存后一个节点的地址
第二步:输出移动地址对应的节点的数据域,输出完毕后,指针继续后移
第三步:以此类推,直到节点的指针域为NULL
void SListPrint(STLNode* phead)
{STLNode* cur = phead;while (cur != NULL)//第一个地址不为空{printf("%d->", cur->data);cur = cur->next;//令cur下一个地址}printf("NULL\n");}
3.4新建一个节点
函数中的 malloc 用于在堆上分配内存。它返回一个指向新分配内存的指针,该指针被转换为 stlNode* 类型并存储在 newnode 变量中。这个新节点被初始化为包含一个 data 字段和一个 next 字段,其中 data 字段被设置为参数 x 的值,而 next 字段被初始化为 NULL。
STLNode* BuySListNode(SLTDataType x)
{STLNode* newnode = (STLNode*)malloc(sizeof(STLNode));newnode->data = x;newnode->next = NULL;
}
3.5尾插
void SListPushBack(STLNode** pphead, SLTDataType x)
//void SListPushBack(STLNode*& pphead, SLTDataType x)
//在 C++语法中可以加&,引用,别名
{STLNode* newnode = (STLNode*)malloc(sizeof(STLNode));newnode->data = x;newnode->next = NULL;// 找尾节点的指针if (*pphead == NULL)//pphead是plist的地址{*pphead = newnode;//在尾部创建新节点}else {STLNode* tail = *pphead;//phead在开始时为空while (tail->next != NULL)//判断下一个指针是否为空{tail = tail->next;//指向下一个节点}// 尾节点,链接新节点tail->next = newnode;}
}
3.6头插
void SListPushFront(STLNode** pphead, SLTDataType x)
{STLNode* newnode = BuySListNode(x);//新建一个节点newnode->next = *pphead;//在第一个地址前建立一个新节点*pphead = newnode;//使新节点变为第一个地址
}
3.7头删
void SListPopFront(STLNode** pphead)
{STLNode* next = (*pphead)->next;//保存下一个地址free(*pphead);//释放空间*pphead = next;//令其指针指向下一个地址
}
3.8尾删
尾删的目的是从给定的单链表中删除最后一个节点,所以分三种情况:
1、链表为空
2、链表只有一个节点
3、链表有多个节点
链表为空:
如果链表为空(*pphead == NULL),那么就没有什么可以删除的内容,所以函数直接返回。
只有一个节点时:
有多个节点时:
如果链表有多个节点,我们需要找到链表的最后一个节点,并删除它。为了找到最后一个节点,我们设置两个指针 prev 和 tail,开始时都指向链表头。然后我们遍历链表,直到找到最后一个节点。找到后,我们释放最后一个节点的内存,然后把倒数第二个节点的 next 设置为 NULL,表示链表最后一个节点已经被删除。
void SListPopBack(STLNode** pphead)
{//1.空//2.一个节点//3.一个以上节点if (*pphead == NULL)//1.空//如果为空,说明链表已经为NULL,没有可以删除的内容了{return;}else if ((*pphead)->next == NULL)//2.一个节点{free(*pphead);//1.先释放*pphead = NULL;//2.把plist置成空}else {//3.一个以上节点STLNode* prev = NULL;STLNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);prev->next = NULL;}
}
3.9查找
其实就是遍历一遍链表,但是只能返回第一次出现的地址。我们查找到节点的地址后就可以通过地址去修改数据域中存储的数据。
TLNode* SListFind(STLNode* phead, SLTDataType x)
{STLNode* cur = phead;while (cur != NULL)//while (cur){if (cur->data = x){return cur;//找到了}cur = cur->next;}return NULL;
}
3.10在pos的前面插入
-
//创建新节点 stlNode* newnode = BuySListNode(x);
-
// 初始时,prev 指向链表的头节点 pphead。 stlNode* prev = *pphead;
-
while (cur != pos) // 这个 while 循环用于找到 pos 节点。
-
prev = prev->next; cur = cur->next; //如果 cur 不等于 pos,那么移动 prev 和 cur。prev 移动到 cur 的下一个节点。cur 移动到下一个节点。
-
prev->next = newnode;//现在,prev 指向 pos 的前一个节点,cur 指向 pos 节点本身。我们将新节点插入到 prev 和 cur 之间。
-
prev->next = newnode; // 然后,我们将新节点的 next 指向 pos,这样新节点就位于 pos 之前了。
void SListInsert(STLNode** pphead, STLNode* pos, SLTDataType x)
{if (pos == *pphead){SListPushFront(pphead, x);}else {STLNode* newnode = BuySListNode(x);STLNode* prev = *pphead;/*while (prev->next != pos)*/STLNode* cur = *pphead;while (cur != pos){prev = prev->next;}prev->next = newnode;newnode->next = pos;}
}
3.11删除pos位置的值
void SListErase(STLNode** pphead, STLNode* pos)
{if (pos == *pphead)//如果要删除的是头节点{SListPopFront(pphead);//头删}else {STLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;//删除指定位置的节点free(pos);//释放要删除节点的内存}
}
四、SList.c
#include"SeqList.h"void SListPrint(STLNode* phead)
{STLNode* cur = phead;while (cur != NULL)//第一个地址不为空{printf("%d->", cur->data);cur = cur->next;//令cur下一个地址}printf("NULL\n");}STLNode* BuySListNode(SLTDataType x)
{STLNode* newnode = (STLNode*)malloc(sizeof(STLNode));newnode->data = x;newnode->next = NULL;
}//尾插
void SListPushBack(STLNode** pphead, SLTDataType x)
//void SListPushBack(STLNode*& pphead, SLTDataType x)
//在 C++语法中可以加&,引用,别名
{STLNode* newnode = (STLNode*)malloc(sizeof(STLNode));newnode->data = x;newnode->next = NULL;// 找尾节点的指针if (*pphead == NULL)//pphead是plist的地址{*pphead = newnode;}else {STLNode* tail = *pphead;//phead在开始时为空while (tail->next != NULL)//判断下一个指针是否为空{tail = tail->next;}// 尾节点,链接新节点tail->next = newnode;}
}void SListPushFront(STLNode** pphead, SLTDataType x)
{STLNode* newnode = BuySListNode(x);newnode->next = *pphead;*pphead = newnode;
}void SListPopFront(STLNode** pphead)
{STLNode* next = (*pphead)->next;//保存下一个地址free(*pphead);//释放空间*pphead = next;//令其指针指向下一个地址
}void SListPopBack(STLNode** pphead)
{//1.空//2.一个节点//3.一个以上节点if (*pphead == NULL){return;}else if ((*pphead)->next == NULL){free(*pphead);//1.先释放*pphead = NULL;//2.把plist置成空}else {STLNode* prev = NULL;STLNode* tail = *pphead;while (tail->next != NULL){prev = tail;tail = tail->next;}free(tail);prev->next = NULL;}
}STLNode* SListFind(STLNode* phead, SLTDataType x)
{STLNode* cur = phead;while (cur != NULL)//while (cur){if (cur->data = x){return cur;//找到了}cur = cur->next;}return NULL;
}// 在pos的前面插入x
void SListInsert(STLNode** pphead, STLNode* pos, SLTDataType x)
{if (pos == *pphead){SListPushFront(pphead, x);}else {STLNode* newnode = BuySListNode(x);STLNode* prev = *pphead;/*while (prev->next != pos)*/STLNode* cur = *pphead;while (cur != pos){prev = prev->next;}prev->next = newnode;newnode->next = pos;}
}
// 删除pos位置的值
void SListErase(STLNode** pphead, STLNode* pos)
{if (pos == *pphead){SListPopFront(pphead);}else {STLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);}
}
五、Test.c
#include"SeqList.h"void TestSList1()
{STLNode* plist = NULL;//先让plist指向空SListPushBack(&plist, 1);SListPushBack(&plist, 2);//插入几个值,用节点存起来SListPushBack(&plist, 3);SListPushBack(&plist, 4);SListPushFront(&plist, 0);SListPrint(plist);SListPopFront(&plist);SListPopFront(&plist);SListPopFront(&plist);SListPrint(plist);SListPopFront(&plist);SListPopFront(&plist);SListPrint(plist);
}
void TestSList3()
{STLNode* plist = NULL;//先让plist指向空SListPushBack(&plist, 1);SListPushBack(&plist, 2);//插入几个值,用节点存起来SListPushBack(&plist, 3);SListPushBack(&plist, 4);SListPrint(plist);/*SListPopBack(&plist);SListPopBack(&plist);SListPrint(plist);*///想在3的前面插入一个数字STLNode* pos = SListFind(plist, 1);if (pos){SListInsert(&plist, pos, 10);}SListPrint(plist);pos = SListFind(plist, 3);if (pos){SListInsert(&plist, pos, 30);}SListPrint(plist);
}void TestSList4()
{STLNode* plist = NULL;//先让plist指向空SListPushBack(&plist, 1);SListPushBack(&plist, 2);SListPushBack(&plist, 3);SListPushBack(&plist, 4);SListPrint(plist);STLNode* pos = SListFind(plist, 3);if (pos){SListErase(&plist, pos);}SListPrint(plist);pos = SListFind(plist, 4);if (pos){SListErase(&plist, pos);}SListPrint(plist);
}int main()
{TestSList4();return 0;
}
今天就先到这了!!!
看到这里了还不给博主扣个:
⛳️ 点赞☀️收藏 ⭐️ 关注!
你们的点赞就是博主更新最大的动力!
有问题可以评论或者私信!!!
关注必回!!!
相关文章:

链表基础知识(一、单链表)
一、链表表示和实现 顺序表的问题及思考 问题: 1. 中间/头部的插入删除,时间复杂度为O(N) 2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。 3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当…...

mysql的ON DELETE CASCADE 和ON DELETE RESTRICT区别
ON DELETE CASCADE 和 ON DELETE RESTRICT 是 MySQL 中两种不同的外键约束级联操作。它们之间的主要区别在于当主表中的记录被删除时,子表中相关记录的处理方式。 ON DELETE CASCADE: 当在主表中删除一条记录时,所有与之相关的子表中…...

如何快速将图片转为excel?
一、打开金鸣表格文字识别软件。 二、点击添加文件按钮,在打开的窗口中选择目标图片,然后点击“打开”,将图片添加进待识别的列表中。 三、点击提交识别或识别全部。 四、识别完成后点击“打开文件”即可打开识别好的结果文件(EXC…...

元编程(Metaprogramming)
本章将介绍第8️⃣种编程范式---元编程,以及它的优缺点、案例分析和小项目的代码示例。 优点 元编程的优点: 灵活性和可重用性:元编程允许在运行时生成代码,使得程序更加灵活和可重用。可以根据需要动态生成代码片段࿰…...

IEEE Transactions on Industrial Electronics工业电子TIE论文投稿须知
一、背景 IEEE TIE作为控制领域的TOP期刊,接收机器人、控制、自动驾驶、仪器和传感等方面的论文,当然范围不止这些,感兴趣的可以自行登录TIE官网查看。所投稿论文必须经过实验验证,偏工程应用类,当然也必须有方法上的…...

Linux--操作系统
1. 常见的操作系统 Windowsmac OSLinuxiOSAndroid 2. 操作系统的定义 操作系统直接运行在计算机上的系统软件, 它是控制硬件和支持软件运行的计算机程序。 3. 操作系统的作用 向下控制硬件向上支持软件的运行,具有承上启下的作用。 4.总结 操作系统…...

HarmonyOS—实现UserDataAbility
UserDataAbility接收其他应用发送的请求,提供外部程序访问的入口,从而实现应用间的数据访问。Data提供了文件存储和数据库存储两组接口供用户使用。 文件存储 开发者需要在Data中重写FileDescriptoropenFile(Uriuri,Stringmode)方法来操作文件…...

Java实现插入排序及其动图演示
插入排序是一种简单直观的排序算法。它的基本思想是将一个待排序的元素插入到已经排序好的序列中的适当位置,从而得到一个新的、元素个数加一的有序序列。 具体的插入排序算法过程如下: 从第一个元素开始,认为第一个元素已经是有序序列。取…...

设计模式——原型模式(创建型)
引言 原型模式是一种创建型设计模式, 使你能够复制已有对象, 而又无需使代码依赖它们所属的类。 问题 如果你有一个对象, 并希望生成与其完全相同的一个复制品, 你该如何实现呢? 首先, 你必须新建一个属于…...

深眸科技以机器视觉高性能优势,为消费电子行业提供优质解决方案
机器视觉技术近年来发展迅速,基于计算机对图像的处理与分析,能够识别和辨别目标物体,被广泛应用于人工智能、智能制造等领域。 机器视觉凭借着高精度、高效率、灵活性和可靠性等优势,不断推进工业企业生产自动化和智能化进程&…...

React setState()的两种书写方法对比
在React中,setState()方法是一个非常重要的函数,用于更新组件的状态。它有两种常见的书写方式:对象解构赋值和使用函数。本文将对比这两种方法,并解释它们的优缺点和适用场景。 首先,让我们来看看对象解构赋值这种方法…...

orb-slam2学习总结
目录 视觉SLAM 1、地图初始化 2、ORB_SLAM地图初始化流程 3、ORB特征提取及匹配 1、对极几何 2、对极约束 (epipolar constraint) 3、基础矩阵F、本质矩阵E 5、单目尺度不确定性 6、单应矩阵(Homography Matrix) 6.1 什么是单应矩…...

通过wireshark判断web漏洞的流量特征
sql注入 定位http协议,通过查找get请求定位到关键字 抓到关键字union select xss 定位get请求的关键字 文件上传 找到对应的上传数据包,追踪tcp流 文件包含、文件读取 查看url找到包含的关键字 在根路径写入一个phpinfo(); 使用../进行目录遍…...

Command ‘npm‘ not found, but can be installed with:sudo apt install npm 解决方案
问题描述 今天在执行 npm install -g npx 报错 Command npm not found, but can be installed with: sudo apt install npm 解决方案 sudo apt-get remove npm sudo apt-get remove nodejs-legacy sudo apt-get remove nodejs sudo rm /usr/bin/node sudo apt-get install …...

【Hadoop_04】HDFS的API操作与读写流程
1、HDFS的API操作1.1 客户端环境准备1.2 API创建文件夹1.3 API上传1.4 API参数的优先级1.5 API文件夹下载1.6 API文件删除1.7 API文件更名和移动1.8 API文件详情和查看1.9 API文件和文件夹判断 2、HDFS的读写流程(面试重点)2.1 HDFS写数据流程2.2 网络拓…...

go-zero开发入门之网关往rpc服务传递数据
go-zero 的网关往 rpc 服务传递数据时,可以使用 headers,但需要注意前缀规则,否则会发现数据传递不过去,或者对方取不到数据。 go-zero 的网关对服务的调用使用了第三方库 grpcurl,入口函数为 InvokeRPC: …...

Word插件-好用的插件-批量插入图片-大珩助手
现有100张图片,需要批量插入word中,并在word中以每页6张图片的形式呈现,请问怎样做? 使用word大珩助手,多媒体-插入图片,根据图片的长宽,选择连续图片、一行2个图或一行3个图,可一次…...

小程序域名SSL证书能用免费的吗?
众所周知,目前小程序要求域名强制使用https协议,否则无法上线。但是对于大多数开发者来说,为每一个小程序都使用上付费的SSL证书,也是一笔不小的支出。那么小程序能使用免费的SSL证书吗? 答案是肯定的。目前市面上可选…...

selenium自动化(中)
显式等待与隐式等待 简介 在实际工作中等待机制可以保证代码的稳定性,保证代码不会受网速、电脑性能等条件的约束。 等待就是当运行代码时,如果页面的渲染速度跟不上代码的运行速度,就需要人为的去限制代码执行的速度。 在做 Web 自动化时…...

uniapp app将base64保存到相册,uniapp app将文件流保存到相册
如果是文件流可以先转base64详情见>uniapp 显示文件流图片-CSDN博客 onDown(){let base64 this.qrcodeUrl ; // base64地址const bitmap new plus.nativeObj.Bitmap("test");bitmap.loadBase64Data(base64, function() {const url "_doc/" new Dat…...

Navicat 技术指引 | 适用于 GaussDB 分布式的服务器对象的创建/设计
Navicat Premium(16.3.3 Windows版或以上)正式支持 GaussDB 分布式数据库。GaussDB分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能,还提供强大的高阶功能(如模型、结构…...

五、HotSpot细节实现
一、并发标记与三色标记 问题:三色标记到底发生在什么阶段,替代了什么。并发标记 1、并发标记( Concurrent Marking) 从 GC Root 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗…...

DRBD分布式存储实验
DRBD DRBD的全称为:Distributed Replicated Block Device (DRBD) 分布式块设备复制 与心跳连接结合使用,构建高可用性(HA)的集群。 实现方式是通过网络来镜像(mirror)整个设备。它允许用户在远程机器上建立一个本地块设备的实时镜像。DRBD负责接收数据…...

go的结构体作为返回值
结构体有两种方式作为返回值 结构体结构体指针 代码 package mainimport ("fmt" )type SS struct {Name stringAge int }func getInfo() (*SS) {var ac SS{}ac.Age 1return &ac }func getInfo1() (aa *SS) {aa.Age 1return }func getInfo2() (SS) {var ac…...

uniapp的subnvue苹果适配(ios)谷歌地图问题
谷歌地图,google地图,调整宽度。这个适配花了点时间,苹果IOS宽度一直无效失灵,赶紧记录分享,很坑。可能所有的ios的subnvue适配都这样。看了网上很多方法无效,最终找到试出答案。 pages.json的配置宽度无效…...

项目实战之RabbitMQ重试机制进行消息补偿通知
🧑💻作者名称:DaenCode 🎤作者简介:啥技术都喜欢捣鼓捣鼓,喜欢分享技术、经验、生活。 😎人生感悟:尝尽人生百味,方知世间冷暖。 文章目录 🌟架构图&#x…...

MySQL之数据库的创建指令
创建数据库 #创建数据库指令: CREATE DATABASE hsp_db1 #创建名字为关键字的数据库,为规避关键字,可以使用反引号 CREATE DATABASE CREATE#删除数据库指令: DROP DATABASE hsp_db1 DROP DATABASE CREATE如果不指定在这里插入代码片…...

[网络安全]批处理(脚本)编写
Windows DOS命令Linux 一.作用: 自上而下成批次处理每一条命令,直到执行到最后一条 二.如何创建批处理: 扩展名:.bat创建办法:新建一个记事本,把扩展名改为 .bat 三.编辑方法: 右击 -编辑 1).一行一个命令 四.批处理命令: pause 暂停 (及时后面有命令,也不执行)echo …...

事件驱动架构 vs. RESTful架构:通信模式对比与选择
1. 通信风格 事件驱动架构(EDA) 是一种异步通信风格,组件之间通过产生和消费事件进行通信。 事件是表示系统中重大变化或事件的消息,并分发给感兴趣的组件。这种通信模型允许系统的不同部分之间进行解耦和动态交互。 组件充当事件…...

代码随想录算法训练营第五十二天| 300 最长递增子序列 674 最长连续递增子序列 718 最长重复子数组
目录 300 最长递增子序列 674 最长连续递增子序列 718 最长重复子数组 300 最长递增子序列 class Solution { public:int lengthOfLIS(vector<int>& nums) {vector<int>dp(nums.size(),1);//以i结尾的最长递增子序列的长度for(int i 0;i < nums.size()…...