链表基础知识(一、单链表)
一、链表表示和实现
顺序表的问题及思考
问题:
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…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
