基本数据结构:链表(list)

Redis 数据结构 Redis 3.0 的 List 对象在数据量⽐较少的情况下,会采⽤「压缩列表」作为底层数据结构的实现,它的优势是节省内存空间,并且是内存紧凑型的数据结构。然后在 Redis 5.0 设计了新的数据结构 listpack,沿⽤了压缩列表紧凑型的内存布局,最终在最新的 Redis 版本,将 Hash 对象和 Zset 对象的底层数据结构实现之⼀的压缩列表,替换成由 listpack 实现。Redis 只有在 Zset 对象的底层实现⽤到了跳表,跳表的优势是能⽀持平均 O(logN) 复杂度的节点查找。 阅读详情

谈到链表之前,先说一下线性表。线性表是最基本、最简单、也是最常用的一种数据结构。线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。线性表有两种存储方式,一种是顺序存储结构,另一种是链式存储结构。

顺序存储结构就是两个相邻的元素在内存中也是相邻的。这种存储方式的优点是查询的时间复杂度为O(1),通过首地址和偏移量就可以直接访问到某元素,关于查找的适配算法很多,最快可以达到O(logn)。缺点是插入和删除的时间复杂度最坏能达到O(n),如果你在第一个位置插入一个元素,你需要把数组的每一个元素向后移动一位,如果你在第一个位置删除一个元素,你需要把数组的每一个元素向前移动一位。还有一个缺点,就是当你不确定元素的数量时,你开的数组必须保证能够放下元素最大数量,遗憾的是如果实际数量比最大数量少很多时,你开的数组没有用到的内存就只能浪费掉了。

我们常用的数组就是一种典型的顺序存储结构,如图1。


链式存储结构就是两个相邻的元素在内存中可能不是相邻的,每一个元素都有一个指针域,指针域一般是存储着到下一个元素的指针。这种存储方式的优点是插入和删除的时间复杂度为O(1),不会浪费太多内存,添加元素的时候才会申请内存,删除元素会释放内存,。缺点是访问的时间复杂度最坏为O(n),关于查找的算法很少,一般只能遍历,这样时间复杂度也是线性(O(n))的了,频繁的申请和释放内存也会消耗时间。

顺序表的特性是随机读取,也就是访问一个元素的时间复杂度是O(1),链式表的特性是插入和删除的时间复杂度为O(1)。要根据实际情况去选取适合自己的存储结构。

链表就是链式存储的线性表。根据指针域的不同,链表分为单向链表、双向链表、循环链表等等。

一、 单向链表(slist)

链表中最简单的一种是单向链表,每个元素包含两个域,值域和指针域,我们把这样的元素称之为节点。每个节点的指针域内有一个指针,指向下一个节点,而最后一个节点则指向一个空值。如图2就是一个单向链表。


一个单向链表的节点被分成两个部分。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址。单向链表只可向一个方向遍历。

我写了一个简单的C++版单向链表类模板,就用这段代码讲解一下一个具体的单向链表该怎么写(代码仅供学习),当然首先你要具备C++基础知识和简单的模板元编程。
完整代码

首先我们要写一个节点类,链表中的每一个节点就是一个节点类的对象。如图3。


代码如下:


template< class T>
class slistNode
{
     public:
    slistNode(){next=NULL;} // 初始化
    T data; // 值
    slistNode* next; // 指向下一个节点的指针
};

第二步,写单链表类的声明,包括属性和方法。

代码如下:


template< class T>
class myslist
{
     private:
    unsigned  int listlength;
    slistNode<T>* node; // 临时节点
    slistNode<T>* lastnode; // 头结点
    slistNode<T>* headnode; // 尾节点
     public:
        myslist(); // 初始化
        unsigned  int length(); // 链表元素的个数
         void add(T x); // 表尾添加元素
         void traversal(); // 遍历整个链表并打印
         bool isEmpty(); // 判断链表是否为空
        slistNode<T>* find(T x); // 查找第一个值为x的节点,返回节点的地址,找不到返回NULL
         void Delete(T x); // 删除第一个值为x的节点
         void insert(T x,slistNode<T>* p); // 在p节点后插入值为x的节点
         void insertHead(T x); // 在链表的头部插入节点
};

第三步,写构造函数,初始化链表类的属性。

代码如下:

template< class T>
myslist<T>::myslist()
{
    node=NULL;
    lastnode=NULL;
    headnode=NULL;
    listlength=0;
}

第四步,实现add()方法。

代码如下:

template< class T>
void  myslist<T>::add(T x)
{
    node= new slistNode<T>(); // 申请一个新的节点
    node->data=x; // 新节点赋值为x
     if(lastnode==NULL) // 如果没有尾节点则链表为空,node既为头结点,又是尾节点
    {
        headnode=node;
        lastnode=node;
    }
     else // 如果链表非空
    {
        lastnode->next=node; // node既为尾节点的下一个节点
        lastnode=node; // node变成了尾节点,把尾节点赋值为node
    }
    ++listlength; // 元素个数+1
}

第五步,实现traversal()函数,遍历并输出节点信息。

代码如下:

template< class T>
void  myslist<T>::traversal()
{
    node=headnode; // 用临时节点指向头结点
     while(node!=NULL) // 遍历链表并输出
    {
        cout<<node->data<<ends;
        node=node->next;
    }
    cout<<endl;
}

第六步,实现isEmpty()函数,判断链表是否为空,返回真为空,假则不空。

代码如下:

template< class T>
bool  myslist<T>::isEmpty()
{
     return listlength==0;
}

第七步,实现find()函数。

代码如下:

template< class T>
slistNode<T>* myslist<T>::find(T x)
{
    node=headnode; // 用临时节点指向头结点
     while(node!=NULL&&node->data!=x) // 遍历链表,遇到值相同的节点跳出
    {
        node=node->next;
    }
     return node; // 返回找到的节点的地址,如果没有找到则返回NULL
}

第八步,实现delete()函数,删除第一个值为x的节点,如图4。


代码如下:

template< class T>
void  myslist<T>::Delete(T x)
{
    slistNode<T>* temp=headnode; // 申请一个临时节点指向头节点
     if(temp==NULL)  return; // 如果头节点为空,则该链表无元素,直接返回
     if(temp->data==x) // 如果头节点的值为要删除的值,则删除投节点
    {
        headnode=temp->next; // 把头节点指向头节点的下一个节点
         if(temp->next==NULL) lastnode=NULL; // 如果链表中只有一个节点,删除之后就没有节点了,把尾节点置为空
        delete(temp); // 删除头节点
         return;
    }
     while(temp->next!=NULL&&temp->next->data!=x) // 遍历链表找到第一个值与x相等的节点,temp表示这个节点的上一个节点
    {
        temp=temp->next;
    }
     if(temp->next==NULL)  return; // 如果没有找到则返回
     if(temp->next==lastnode) // 如果找到的时候尾节点
    {
        lastnode=temp; // 把尾节点指向他的上一个节点
        delete(temp->next); // 删除尾节点
        temp->next=NULL;
    }
     else // 如果不是尾节点,如图4
    {
        node=temp->next; // 用临时节点node指向要删除的节点
        temp->next=node->next; // 要删除的节点的上一个节点指向要删除节点的下一个节点
        delete(node); // 删除节点
        node=NULL;
    }
}

第九步,实现insert()和insertHead()函数,在p节点后插入值为x的节点。如图5。



 

代码如下:

template< class T>
void  myslist<T>::insert(T x,slistNode<T>* p)
{
     if(p==NULL)  return;
    node= new slistNode<T>(); // 申请一个新的空间
    node->data=x; // 如图5
    node->next=p->next;
    p->next=node;
     if(node->next==NULL) // 如果node为尾节点
    lastnode=node;
}
template< class T>
void  myslist<T>::insertHead(T x)
{
    node= new slistNode<T>();
    node->data=x;
    node->next=headnode;
    headnode=node;
}

最终,我们完成一个简单的单向链表。此单向链表代码还有很多待完善的地方,以后会修改代码并不定时更新。

二、 双向链表

双向链表的指针域有两个指针,每个数据结点分别指向直接后继和直接前驱。单向链表只能从表头开始向后遍历,而双向链表不但可以从前向后遍历,也可以从后向前遍历。除了双向遍历的优点,双向链表的删除的时间复杂度会降为O(1),因为直接通过目的指针就可以找到前驱节点,单向链表得从表头开始遍历寻找前驱节点。缺点是每个节点多了一个指针的空间开销。如图6就是一个双向链表。



三、 循环链表

循环链表就是让链表的最后一个节点指向第一个节点,这样就形成了一个圆环,可以循环遍历。单向循环链表可以单向循环遍历,双向循环链表的头节点的指针也要指向最后一个节点,这样的可以双向循环遍历。如图7就是一个双向循环链表。

魔兽世界怀旧服宏命令全解析:从自动换装到智能判定,老玩家才知道的黑科技 本文全面解析魔兽世界怀旧服宏命令的高级应用,从自动换装到智能判定,揭秘老玩家专属的黑科技技巧。深入探讨怀旧服特有的宏命令机制,包括施法队列、装备切换系统和职业专属技巧,帮助玩家在经典旧世版本中提升战斗效率。 阅读详情

相关推荐

用迅为RK3588开发板DIY无人机飞控:从电路设计到PID调参全记录

本文详细记录了使用迅为RK3588开发板DIY无人机飞控的全过程,从硬件选型、PCB设计到实时系统构建和PID调参。RK3588核心板凭借其强大的AI算力和丰富接口,成为高性能无人机控制解决方案的理想选择。文章还分享了视觉导航系统集成和实际飞行测试经验,为硬件爱好者提供实用参考。

weixin_29053577的博客 426

C++数据结构——链表

链表是一种动态数据结构,它通过指针将一组零散的内存块串联起来使用。它作为基础数据结构,其实现质量直接影响程序性能和稳定性。

Daniel0429的博客 4379

Docker Desktop重装避坑:从卸载到安装的完整流程(Win10/Win11适用)

本文详细介绍了在Windows 10/11系统上彻底卸载和重新安装Docker Desktop的完整流程,包括深度清理残留文件、注册表项和虚拟磁盘,以及安装前的环境准备和安装后的验证步骤,帮助用户避免常见问题并优化Docker环境配置。

julia4scientist的博客 1107

手把手教你写链表,小学生看了都说好~

摘要:明明我们在之前已经接触了数组,感到数组已经是万能的数据存储位置了。但是,如果我们一直在使用比较复杂的数据(也就是比较多的数据时),肯定会感到很反感。因为对于数组这种数据结构,在你自己使用之前,一定要对其大小进行一番定义。这样一来,它的存储空间在数据处理过程中便显得极为不方便。因为谁也不想对将要处理的数据做一个空间的预算,这几乎是所有程序员都很忌讳的,并且还要让其空间足够大,这样才能满足我们的要求(但如果分配的太多,难免会浪费内存)。 所以,这就是为啥你要用链表、学链表。链表是一种数据结构,它弥补了数组

weixin_39266374的博客 910

别再只用串口助手了!用LabVIEW给STM32F103C8T6做个专属上位机(附完整源码)

本文详细介绍了如何利用LabVIEW为STM32F103C8T6开发定制化上位机,替代传统串口助手,实现高效数据交互。内容涵盖硬件架构设计、STM32固件优化、LabVIEW上位机开发技巧及工业级应用案例,特别适合需要实时数据采集与处理的工控场景。

weixin_42524762的博客 367

多核系统核间通信(IPC)设计:从硬件机制到软件实现

在多核处理器与嵌入式系统设计中,核间通信(IPC)是实现计算资源协同工作的核心技术。其原理基于共享内存、硬件邮箱与中断等底层硬件机制,通过构建结构化的消息传递通道,实现核间高效、可靠的数据交换。该技术的核心价值在于打破多核间的壁垒,将分散的计算单元整合为有机整体,从而显著提升系统整体性能与响应能力。在工程实践中,IPC广泛应用于智能座舱、边缘AI网关、实时控制系统等场景,用于处理如仪表显示、AI推理、实时控制等复杂任务。本文聚焦于基于共享内存与VRING的消息队列实现,深入剖析缓存一致性、内存屏障等关键问题

weixin_27006759的博客 593

基本数据结构:链表(list) - C小加 - C++博客

基本数据结构:链表(list) - C小加 - C++博客 基本数据结构:链表(list) - C小加 - C++博客基本数据结构:链表(list)作者:C小加更新时间:2012-7-31谈到链表之前,先说一下线性表。线性表是最基本、最简单、也是最常用的一种数据结...

526

YOLOv8改进 | Conv篇:基于FasterBlock与C2f结构的二次创新,参数量下降70万【附保姆级代码】

通过引入FasterBlock二次创新C2f结构,我们成功地对YOLOv8模型进行了优化,显著降低了其参数量,并提升了推理速度,而精度保持不变甚至有所提升。该模型在多个实际应用场景中展现出了优异的性能,尤其是在嵌入式设备、移动端和自动驾驶等领域,具有广泛的应用潜力。未来的研究将进一步优化模型结构,探索更多的应用场景,为目标检测技术的发展做出贡献。

一键难忘的博客 3214

基本的数据结构:单链表(Singly Linked List)

一.基本的数据结构单链表(Singly Linked List) 什么是单链表? 下面是百度百科给出的官方解释: 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。 其实通俗的来讲,单链表就是结构体变量...

weixin_44795839的博客 3626

SSE 传输过程中,丢失空格和回车符,怎么解决?

Server - Sent Events(SSE)即服务器发送事件,是 HTML5 中的一项 Web API,用于实现服务器向客户端浏览器推送实时数据的功能。

934

卷积码Viterbi译码算法原理及C语言实现

具体而言,卷积码的编码过程是将输入数据和编码器中的状态进行异或操作,输出码字为各个状态对应的结果。Viterbi译码算法是一种最大似然译码算法,根据接收到的含有噪声的码字,寻找出最有可能的原始数据序列。(2) 递推计算:根据接收到的码字和当前状态,计算下一时刻各个状态的路径度量,并记录每个状态的前驱状态。以上代码实现了一个简单的卷积码Viterbi译码算法,通过接收到的码字进行译码,并输出解码结果。(1) 初始化:将所有状态的路径度量初始化为一个很大的值,选择路径度量最小的状态作为起始状态。

2301_79331421的博客 825

基本数据结构:链表(list)(1)

基本数据结构:链表(list) 作者:C小加 更新时间:2012-7-31 谈到链表之前,先说一下线性表。线性表是最基本、最简单、也是最常用的一种数据结构。线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的。线性表有两种存储方式,一种是顺序存储结构,另一种是链式存储结构。 顺序存储结构就是两个相邻的元素在内存中也...

weixin_34245749的博客 132

YOLO26 数据标注工具:LabelImg、Label Studio实战指南:主流标注工具的安装配置与使用技巧,高效完成目标检测数据标注

本文介绍了目标检测中数据标注的核心地位与常用工具。数据标注质量直接影响模型性能,需要关注准确性、一致性等维度。文章详细讲解了边界框、类别标签等基本概念,并对比了LabelImg、Label Studio等主流标注工具的特点。重点介绍了LabelImg的轻量级、易用性等优势,以及Label Studio的协作功能,为不同规模项目提供标注工具选择建议。

本博客聚焦 YOLOv11 全流程落地,涵盖架构优化、数据集处理、训练技巧与多场景部署,兼及国产数据库、Java 开发与 AI 模型应用,内容兼顾理论与工程实践,为开发者提供系统干货,助力高效提升技术能力。 1174

基本数据结构:链表(list)(2)

接上篇 第九步,实现insert()和insertHead()函数,在p节点后插入值为x的节点。如图5。 代码如下: template<classT>voidmyslist<T>::insert(Tx,slistNode<T>*p){if(p==NULL)return;...

weixin_33762130的博客 77

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Linux内核数据结构之链表

链表一、链表结构1、单向链表2、双向链表3、环形链表二、Linux内核中链表的实现 和其他很多大型项目一样,Linux内核实现了很多通用的数据结构,而且提倡大家在开发时重用。 一、链表结构 链表是一种存放和操作可变数量元素(常称为节点)的数据结构。链表和静态数组的不同之处在于,链表所包含的元素都是动态创建并插入链表的,在编译的时候不必知道具体需要创建多少元素,所以它们在内存中无须占用连续内存区。正是因为元素不连续地存放,所以各元素需要通过某种方式被连接在一起。 1、单向链表 每个元素拥有一个指向下一个元素的

年少生而为人 726

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

C++链表解析:从基础原理到高级应用,全面掌握链表的使用

C++链表解析:从基础原理到高级应用,全面掌握链表的使用

探索C++编程的奥秘,分享深入的技术见解和实践,旨在激发读者创造力与解决问题的思维。 1880

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Redis学习笔记--Redis基本类型及其数据结构

什么是Redis? Redis(全称:Remote Dictionary Server 远程字典服务)是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted ...

安天然的博客 1017
上一篇: 大话设计模式——序
下一篇: == equals new String("abc")
JJ_wang
博客等级 码龄14年 2粉丝 10原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值