链表基础知识(一、单链表)
一、链表表示和实现
顺序表的问题及思考
问题:
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…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案
这个问题我看其他博主也写了,要么要会员、要么写的乱七八糟。这里我整理一下,把问题说清楚并且给出代码,拿去用就行,照着葫芦画瓢。 问题 在继承QWebEngineView后,重写mousePressEvent或event函数无法捕获鼠标按下事…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...