浅谈【数据结构】链表之其他形态

news2024/11/14 21:54:38

目录

1、带头结点的链表

2、创建步骤

3、循环链表

3.1创建循环链表

3.2循环链表的遍历

3.3链表中的闭环

4、静态链表

4.1静态链表初始化


谢谢帅气美丽且优秀的你看完我的文章还要点赞、收藏加关注

没错,说的就是你,不用再怀疑!!!

希望我的文章内容能对你有帮助,一起努力吧!!!


1、带头结点的链表

引入:链表的操作起来比较舒服,但是如果记录(获取)链表长度比较麻烦。需要遍历链表,一个一个 计数,比较费时间(占CPU资源)

所以可以不可以用一个比较特殊一点的结点来保存链表相关的信息?->头结点

  • 头结点

头节点其实就是一个特殊的结点,它的类型和其他的结点不一样的,它是专门用来存储链表的一些属性 信息。

struct head {

        int length; // 链表长度

        结点类型*first; // 首结点地址

        结点类型*final; // 尾结点地址

};

注意:

  • 头节点不参与链表的长度计数
  • 头节点也是不参与链表遍历
  • 头节点也不会作为链表的结点

带头结点链表,头节点是在链表中存储链表信息的作用。

注意:当对链表进行增删操作的时候需要对头节点进行及时的更新。

2、创建步骤

  • 从无到有
    • 创建一个头结点
  • 从少到多
    • 一个一个正常增加(注意:及时更新头节点的信息)

图示:

***增加节点***

***删除节点(4种情况)***

代码示例:

#include <iostream>

// 结点类型
struct node
{
    int data;
    struct node *next;
};

// 头节点类型
struct head
{
    int length;
    struct node *first;
    struct node *final;
};

/*
    @brief 创建链表
    @return 链表的头节点地址
*/
struct head *createNewList()
{
    // 申请一个空间
    struct head *newList = new struct head;

    // 初始化链表属性信息
    newList->length = 0;
    newList->first  = nullptr;
    newList->final  = nullptr;

    // 返回创建好的链表头节点地址
    return newList;
}

/*
    @brief 头插法增加结点进链表
    @param list 需要增加结点的链表的地址
    @param data 新结点数据
*/
void addNodeHead(struct head*list,int data)
{
    // 先判断链表是否存在
    if(!list)
        return;

    // 申请新结点空间
    struct node *newNode = new struct node;

    // 结点数据的初始化
    newNode->data = data;
    newNode->next = nullptr;

    // 开始头插法
    // 让新节点的next指针指向头节点里面的首结点
    newNode->next = list->first; // list->first 表示首结点地址

    // 更新头结点中首结点(first)和长度
    list->first = newNode; // 让newNode成为新的首结点
    list->length++;// 更新链表长度
}

/*
    @brief 尾插法增加结点进链表
    @param list 需要增加结点的链表的地址
    @param data 新结点数据
*/
void addNodeTail(struct head*list,int data)
{
    // 先判断链表是否存在
    if(!list)
        return;

    // 申请新结点空间
    struct node *newNode = new struct node;

    // 结点数据的初始化
    newNode->data = data;
    newNode->next = nullptr;

    // 进行尾插法增加
    list->final->next = newNode; // list->final 表示尾结点地址

    // 更新头结点中尾结点(final)和长度
    list->final = newNode;
    list->length++;
}


/*
    @brief 删除一个置顶的结点
    @param list 需要删除结点的链表地址
    @param data 需要删除的那个结点的数据
    @return true表示成功,false表示失败
*/
bool delListNode(struct head*list,int data)
{
    // 先判断链表是否存在
    if(!list)
        return;

    // 遍历查找元素
    struct node *node_ptr = list->first;
    // 一个结点的时候
    if(list->first->data == data&&list->length == 1)
    {
        // 直接释放空间
        delete list->first;

        // 更新首尾结点信息
        list->first = nullptr;
        list->final = nullptr;

        list->length--;
        return ;
    }

    // 判断是否为首结点且数量大于1
    if(list->first->data == data&&list->length > 1)
    {
        // 更新首结点
        list->first = node_ptr->next; 
        node_ptr->next = nullptr;
        delete node_ptr;
        list->length--;
        return true;
    }
    while(node_ptr->next)
    {
        // 找到了需要删除的元素
        if(node_ptr->next->data == data)
        {
            // 判断是不是尾结点且数量大于1
            if(node_ptr->next == list->final&&list->length > 1)
            {
                // 更新尾结点
                list->final = node_ptr; 

                // 释放需要删除的元素,再置空
                // 原因:node_ptr并非需要删除的元素,它是被删除元素的前一个元素。
                delete node_ptr->next;
                node_ptr->next = nullptr;
                list->length--;
                return true;
            }
            else if(list->length > 1)
            {
                // 临时存储被删除元素的地址
                struct node *delNode = node_ptr->next;

                // 更新被被删除元素的next指针:实际就是跳过被删除元素
                node_ptr->next = node_ptr->next->next;

                // 断开被删除元素的链接
                delNode->next = nullptr;

                // 释放元素空间
                delete delNode;
                list->length--;
                return ;
            }
        }
    }
}

3、循环链表

循环链表:第一个数据结点和最后一个数据结点相连链表

循环单链表:最后一个数据结点往后就到了第一个数据结点

循环双链表:最后一个数据结点往后就到了第一个数据结点,第一个数据结点往前走就到了最后数据结 点

3.1创建循环链表

3.2循环链表的遍历

用两个指针,一个指针跑,另一个指针不动,当两个指针重叠的时候就跑完了。

代码示例:

#include <iostream>


typedef struct node 
{
    int data;
    struct node *next;
}NodeType;


/*
    @brief 为循环链表增加结点
    @param list 需要增加结点的循环链表
    @param data 新结点数据
*/
NodeType *addNewNode(NodeType *list,int data)
{
    // 如果为空作为第一个结点插入
    if(!list)
    {
        list = new NodeType;
        list->data = data;

        // 自己指向自己,这个就是形成循环的关键了
        list->next = list;
        return list; 
    }
    // 不是第一结点
    NodeType *newNode = new NodeType;
    newNode->data = data;
    newNode->next = nullptr;

    // 如果list指向的是最后加入链表结点(头插/尾插)
    newNode->next = list->next;
    list->next = newNode;

    return newNode;
}


/*
    @brief 创建循环链表
    @return 返回新循环链表的地址
*/
NodeType *createLoopList()
{
    NodeType *loopList = nullptr;

    int data = -1;
    do
    {
        std::cin >> data;
        if(data == -1)
            break;

        // 增加结点
        loopList = addNewNode(loopList,data);
    }while(1);
    return loopList;
}

/*
    @brief 打印循环链表
*/
void PrintLoopList(NodeType *looplist)
{
    if(!looplist)
    {
        std::cout << "空的" << std::endl;
        return;
    }
    
    NodeType *node_ptr = looplist;
    do
    {
        std::cout << node_ptr->data << std::endl;
        node_ptr = node_ptr->next;
    }while(node_ptr != looplist);
}

int main()
{
    NodeType*looplist = createLoopList();

    PrintLoopList(looplist);

    return 0;
}

3.3链表中的闭环

链表的尾结点指向了链表中的任意一个随机的结点

求闭环的算法

  • 使用两个指针
    • 一个移动步数快
    • 一个移动步数慢
  • 如果两者发生重叠 就形成闭环
  • 如果其中一个指针出现空指针的情况 ,就是没有环的

代码示例:

***形成闭环示例***

#include <iostream>


typedef struct node 
{
    int data;
    struct node *next;
}NodeType;


/*
    @brief 为循环链表增加结点
    @param list 需要增加结点的循环链表
    @param data 新结点数据
*/
NodeType *addNewNode(NodeType *list,int data)
{
    // 如果为空作为第一个结点插入
    if(!list)
    {
        list = new NodeType;
        list->data = data;

        // 自己指向自己,这个就是形成循环的关键了
        list->next = list;
        return list; 
    }
    // 不是第一结点
    NodeType *newNode = new NodeType;
    newNode->data = data;
    newNode->next = nullptr;

    // 如果list指向的是最后加入链表结点(头插/尾插)
    newNode->next = list->next;
    list->next = newNode;

    return newNode;
}


/*
    @brief 创建循环链表
    @return 返回新循环链表的地址
*/
NodeType *createLoopList()
{
    NodeType *loopList = nullptr;

    int data = -1;
    do
    {
        std::cin >> data;
        if(data == -1)
            break;

        // 增加结点
        loopList = addNewNode(loopList,data);
    }while(1);
    return loopList;
}

/*
    @brief 打印循环链表
*/
void PrintLoopList(NodeType *looplist)
{
    if(!looplist)
    {
        std::cout << "空的" << std::endl;
        return;
    }
    
    NodeType *node_ptr = looplist;
    do
    {
        std::cout << node_ptr->data << std::endl;
        node_ptr = node_ptr->next;
    }while(node_ptr != looplist);
}

/*
    求闭环的算法
*/
bool IsHaveCircle(NodeType *looplist)
{
    if(!looplist)
        return true;

    
    // 是否有圈
    NodeType *fast = looplist;    // 慢
    NodeType *slow = looplist;    // 快

    // 循环操作
    do
    {
        // 移动指针
        if(fast->next)
            fast = fast->next->next;   // 移动两步
        else  // 如果不能移动表示其中一个next是为空
            return false;
        
        slow = slow->next; // 移动一步

        // 判断有没有为空的指针
        if(!fast||!slow) // 只要任意一个指针为空那么就没有环
            return false;
    }while(fast != slow);

    // 相等退出,就是有环
    return true;
}

int main()
{

    NodeType *looplist = createLoopList();

    if(IsHaveCircle(looplist))
        std::cout << "有环" << std::endl;
    else
        std::cout << "没环" << std::endl;


    // destoryList();
    return 0;
}

***无闭环示例***

/*
    无头结点的单链表
*/



#include <iostream>

/*
    结点类型
*/
typedef struct nodeData
{
    // 用来存储数据的空间(成员)称为:数据域
    int data;       

    // 用来保存其他结点的地址(关系)称为:指针域
    struct nodeData *next;
}NodeType;


/*
    @brief:创建一个新链表
    @return:返回新链表的首结点的地址
*/
struct nodeData *createNewList()
{
    // 新链表的首结点指针
    struct nodeData *newList = nullptr;

    // 循环通过数据不断的去增加新结点到链表中
    while(1)
    {
        int data = -1;
        // 通过键盘获取数据
        std::cin >> data;

        // 判断退出条件
        if(data == -1)
            break;

        // 申请了一个新结点的空间
        struct nodeData *newNode = new struct nodeData;
        newNode->data = data; // 将通过键盘获取到的数据存入结构体的数据域中
        newNode->next = nullptr; // 因为它是一个新结点,暂时是没有后继结点

        // 增加到链表里面:向后增加(尾插法)
        // 做第一次判断:链表中有没有结点
        if(newList == nullptr)
        {
            // 如果newList是nullptr说明该链表里面为空,当前的新节点就是首届点
            newList = newNode;
            continue; // 继续增加新结点
        }

        // 搞一个临时指针,来指向首结点
        struct nodeData *node_ptr = newList;

        // 找尾结点
        while(node_ptr->next)node_ptr = node_ptr->next;
        
        // 到这个位置 node_ptr 此时指向的结点是尾结点
        // 就可以把newNode作为尾结点的后继结点添加到链表里面去了
        node_ptr->next = newNode;
    }
    return newList;
}

// 打印链表(遍历方法)
void printList(struct nodeData *list)
{
    // 判断是不是空链表
    if(list == nullptr)
    {
        std::cout << "链表为空" << std::endl;
        return;
    }

    std::cout << "List( ";
    // 如果不为空打印链表元素
    while(list) // 只要list不为空就一直循环
    {
        // list本来就可以表示首结点
        std::cout << list->data << " ";

        // 让list移动到下一个结点
        list = list->next;
    }
    std::cout << ")" << std::endl;
}


bool IsHaveCircle(NodeType *looplist)
{
    if(!looplist)
        return true;

    
    // 是否有圈
    NodeType *fast = looplist;    // 慢
    NodeType *slow = looplist;    // 快

    // 循环操作
    do
    {
        // 移动指针
        if(fast->next)
            fast = fast->next->next;   // 移动两步
        else  // 如果不能移动表示其中一个next是为空
            return false;
        
        slow = slow->next; // 移动一步

        // 判断有没有为空的指针
        if(!fast||!slow) // 只要任意一个指针为空那么就没有环
            return false;
    }while(fast != slow);

    // 相等退出,就是有环
    return true;
}


int main()
{
    // 不在栈空间里面申请结点空间
    // 创建一个链表
    struct nodeData *newList = createNewList();

    // 打印链表元素
    printList(newList); // 传入的值是newList存储的地址,并非newList自己的地址
    
    if(IsHaveCircle(newList))
        std::cout << "有环" << std::endl;
    else
        std::cout << "没环" << std::endl;

    return 0;
}

4、静态链表

静态链表是一种使用数组来实现链表概念的数据结构。它通过在每个元素中存储下一个元素的索引来实 现链式存储,从而在有限的空间内实现对大量元素的有效管理。

普通链表

struct node {

        int data;

        struct node *next;

}; // 结点

静态链表

struct node {

        int data;

        int next; // 不是指针,而是下标

};

struct node static_list[100]={0};

4.1静态链表初始化

for(int i = 0;i < 100;i ++)

{

        static_list[i].data = i+1;

        if(i!=99)

                static_list[i].next = i+1;

        else

                 static_list[i].next = -1; // 下标不会出现-1

}

插入元素

// 尾插法

int i = 0;

for(i < 100;i++)

if(static_list[i].next == -1)

        break;

// 找空位

for(int l = 0; l < 100;l++)

{

         if(static_list[l] == NULL)

         {

                static_list[l] = newNode;

                static_list[i].next = l;

        }

}

注意事项:

  • 静态链表是一种在内存空间有限时使用的数据结构。
  • 使用数组和指针来实现链表的功能。
  • 初始化静态链表时,需要正确设置每个节点的 data 和 next 指针。
  • 在向静态链表添加元素时,需要检查是否已满。
  • 访问静态链表元素时,要确保索引有效。
  • 静态链表的大小是固定的,因此在设计时需要合理估计最大元素数量。

代码实例:

#include <iostream>

struct node
{
    int data;   // 数据
    int next_index; // 下标
};

#define MAXLEN 20


void addNode(struct node *staticlist[],int data)
{
    // 插入元素
    if(staticlist[0] == nullptr)
    {
        staticlist[0] = new struct node;
        staticlist[0]->data = 1;
        staticlist[0]->next_index = -1;
        return;
    }

    std::cout << __LINE__ << std::endl;

    // 找尾元素插入
    int pos = 0; // 第一个
    while(1)
    {
        if(staticlist[pos]->next_index == -1)
            break;
        pos = staticlist[pos]->next_index;
    }
    
    // 找到尾元素了
    int newPos = 0;
    while(staticlist[newPos] != nullptr)newPos++;

    // 将新结点加入
    staticlist[newPos] = new struct node;
    staticlist[newPos]->data = data;
    staticlist[newPos]->next_index = -1;

    staticlist[pos]->next_index = newPos;
}

int main()
{
    // 新建一个静态链表
    struct node *staticlist[MAXLEN] = {0};

    for(int i = 0;i < 10;i++)
        addNode(staticlist,i+2);

    // 正常打印
    for(int i = 0;i < MAXLEN;i++)
        std::cout << staticlist[i] << " ";
    std::cout << std::endl;

    int pos = 0;
    // 链表遍历形式打印
    do
    {
        std::cout << staticlist[pos]->data << " ";
        pos =staticlist[pos]->next_index;
    } while (pos != -1);
    std::cout << std::endl;

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2070954.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在群晖上安装Git客户端

什么是 Git &#xff1f; Git 是一个分布式版本控制系统&#xff0c;提供了命令行界面&#xff08;CLI&#xff09;以及图形用户界面&#xff08;GUI&#xff09;两种方式进行操作。与 CVS、Subversion 一类的集中式版本控制工具不同&#xff0c;它采用了分布式版本库的作法&am…

LinkedList添加和删除方法的源码分析超详细

我们基于下面这段代码进行分析 进入add方法后调用的是linkLast方法 这是add方法真正的底层逻辑&#xff0c;首先将last值赋给l节点&#xff0c;此时的l为null&#xff0c;然后再创建新节点的时候将l作为前向指针传入&#xff0c;将节点中的next指针也赋值为null&#xff0c;之后…

PHP反序列化二

1.反序列化基础利用 2.pop链 POP链: POP(面向属性编程&#xff09;链是指从现有运行环境中寻找一系列的代码或指令调用&#xff0c;然后根据需求构造出一组连续的调用链。 反序列化利用就是要找到合适的POP链。其实就是构造一条符合原代码需求的链条&#xff0c;去找到可以控…

基于YOLOv8的无人机高空红外(HIT-UAV)检测算法,新的混合型扩张型残差注意力(HDRAB)助力涨点(二)

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文内容&#xff1a;针对基于YOLOv8的无人机高空红外&#xff08;HIT-UAV&#xff09;检测算法进行性能提升&#xff0c;加入各个创新点做验证性试验。 &#x1f4a1;&#x1f4a1;&#x1f4a1;一种新的混合型扩张型残差注意力(HDRAB…

Springboot基础------控制器、过滤器、拦截器、监视器

控制器 控制器用于接收和响应用户发送的请求&#xff0c;一个控制器类需要被Controller注解&#xff1a;&#xff08;即controller层&#xff09; Controller class TestController { }注&#xff1a; RestController实际上是Controller和ResponseBody的简化版&#xff0c;被…

数据仓库系列 3:数据仓库的主要组成部分有哪些?

你是否曾经好奇过,当你在网上购物或使用手机应用时,背后的数据是如何被存储和分析的?答案就在数据仓库中。本文将为你揭开数据仓库的神秘面纱,深入探讨其核心组成部分,以及这些组件如何协同工作,将海量数据转化为有价值的商业洞察。 目录 引言:数据仓库的魔力1. 数据源和数据…

STM32 | STM32 FLASH第十二天(实现代码STM32CUBEMX)

点击上方"蓝字"关注我们 01、FLASH >>> 1、STM32 FLASH Flash内部存储器,内存器件的一种,是一种非易失性( Non-Volatile )内存。 flash闪存是非易失存储器,可以对称为块的存储器单元块进行擦写和再编程。任何flash器件的写入操作只能在空或已擦除的单元…

mac 虚拟机PD19运行E-prime实验遇到E-prime unable to set display mode:0*80004001问题解决

作者&#xff1a;50% unable to set display mode问题 总结&#xff1a; 1. 修改该Experiment的Devices中的Dispaly为640*680,或800*600。 2. 右键开始菜单中的E-Studio程序&#xff0c;打开文件所在位置&#xff0c;然后右键该文件&#xff0c;选择属性-兼容性&#xff0c;勾选…

【内网渗透】ICMP隧道技术,ICMP封装穿透防火墙上线MSF/CS

~ 会当凌绝顶&#xff0c;一览众山小 ~ 前言 博客主页&#xff1a;h0ack1r丶羽~ 从0到1~ 渗透测试中&#xff0c;如果攻击者使用各类上层隧道(例如&#xff1a;HTTP隧道、DNS隧道、常规正/反向端口转发等)进行的操作都失败了&#xff0c;常常会通过ping命令访问远程计算机&am…

医疗多模态大模型是什么?医学多模态模型总结:算法其实很简单,拼的就是硬件算力的问题!多模态大模型(医疗影像分析)

概念 医学多模态大模型是指利用多种不同的医学数据源和模型&#xff0c;通过深度学习和人工智能技术&#xff0c;构建一个综合性的大型模型&#xff0c;以实现更加准确和全面的医学数据分析和预测。 这种模型可以同时处理多种医学数据类型&#xff0c;如医学图像、病历文本、…

自动驾驶---各大车企的端到端之旅

1 背景 端到端技术的落地速度确实有些超出预料&#xff0c;随着以ChatGPT为代表的AI大模型的快速发展&#xff0c;使得自动驾驶系统也能够像人一样进行“思考”&#xff0c;推动了自动驾驶技术的迭代升级。 特斯拉端到端技术的落地&#xff0c;自动驾驶在这一技术上的变化使得自…

<C++> 二叉搜索树

目录 二叉搜索树 1. 概念 2. 二叉搜索树操作 2.1 基础结构 2.2 非递归版 1. 查找 2. 插入 3. 删除 2.3 递归版 1. 查找 2. 插入 3. 删除 2.4 拷贝构造函数 2.5 赋值运算符重载 2.6 析构函数 2.7 完整代码 3. 二叉搜索树的应用 4. 二叉搜索树的性能 二叉搜索树 1. 概念 二叉搜索…

机器学习算法那些事 | 这是我见过最通俗易懂的SVD(奇异值分解)算法介绍

本文来源公众号“机器学习算法那些事”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;这是我见过最通俗易懂的SVD&#xff08;奇异值分解&#xff09;算法介绍 线性代数是机器学习领域的基础&#xff0c;其中一个最重要的概念是奇…

手机号归属地查询如何用Java进行调用

一、什么是手机号归属地查询接口&#xff1f; 手机号归属地查询接口又叫手机号归属地、手机号信息查询、手机号查询&#xff0c;通过手机号查询归属地信息、是否虚拟运营商等。该接口可支持三大运营商&#xff0c;移动、电信、联通等。 二、手机号归属地查询接口适用场景有哪…

OpenCV+Python识别机读卡

背景介绍 正常机读卡是通过读卡机读取识别结果的&#xff0c;目前OpenCV已经这么强大了&#xff0c;尝试着用OpenCVPython来识别机读卡。要识别的机读卡长这样&#xff1a; 我们做以下操作&#xff1a; 1.识别答题卡中每题选中项结果。 不做以下操作&#xff1a; 1.不识别准…

【数据分析:RFM客户价值度模型】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是书生♡&#xff0c;本阶段和大家一起分享和探索大数据技术RFM客户价值度模型&#xff0c;本篇文章主要讲述了&#xff1a;RFM客户价值度模型等等。欢迎大家一起探索讨论&#xff01;&#xff01;&#xff01…

大数据-99 Spark 集群 Spark Streaming DStream 文件数据流、Socket、RDD队列流

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

GATK ReadsPathDataSource类介绍

GATK(Genome Analysis Toolkit)是一个广泛使用的基因组分析工具包,它的核心库之一是htsjdk,用于处理高通量测序数据。在GATK中,ReadsPathDataSource类是负责管理和提供读取高通量测序数据文件(如BAM、SAM、CRAM)的类。 常见使用场景 数据加载:在GATK的基因组分析工具链…

MySQL的MRR(Multi-Range Read)优化原理详解

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

LeetCode:反转区间内的链表

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 反转区间内的链表题目链接方法一&#xff1a;拆开反转…