数据结构与算法-单链表

news2025/3/17 1:26:39

链表

参考学习:B站-逊哥带你学编程

在这里插入图片描述

单链表

单链表-存储结构

typedef int ElemType;

typedef struct node{
    ElemType data;
    struct node *next;
}Node;

单链表-初始化

Node *initList()
{
    Node *head = (Node *)malloc(sizeof(Node));
    head->data = 0;
    head->next = NULL;
    return head;
}

单链表-头插法

int insertHead(Node *L, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); //分配内存
    p->data = e;  //赋值
    p->next = L->next; //插入的节点指向向头节点指向的节点
    L->next = p; //头节点指向新节点
}

图示:

在这里插入图片描述

单链表-遍历

void listNode(Node *L) //传入头节点
{
    Node *p = L->next; //指向第一个节点 有数据的节点
    while(p != NULL)  //若不为空
    {
        printf("%d ", p->data); //输出节点的值
        p = p->next;    //指向下一个节点
    }
    printf("\n");   //换行
}

单链表-尾插法

Node *get_tail(Node *L)
{
    Node *p = L;
    while(p->next != NULL) //找到最后一个节点
    {
        p = p->next; //指向下一个节点
    }
    return p; //返回最后一个节点
}


Node* insertTail(Node *tail, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); //分配内存
    p->data = e; //赋值
    tail->next = p; //最后一个节点指向新节点
    p->next = NULL; //新节点指向空
    return p; //返回新节点
}

图示:
在这里插入图片描述

单链表-在指定位置插入数据

//插入节点
int insertNode(Node *L, int pos, ElemType e)
{
    Node *p = L;//用于保存要插入的位置的前一个节点
    int i = 0;
    while(i < pos-1) //找到要插入的位置
    {
        p = p->next; //指向下一个节点
        i++;
        if (p == NULL) //若为空
        {
            printf("Position is invalid\n"); //位置无效
            return 0;
        }
    Node *q = (Node *)malloc(sizeof(Node)); //分配内存
    q->data = e; //赋值
    q->next = p->next; //新节点指向下一个节点
    p->next = q; //前一个节点指向新节点
    return 1;
}

图示:

在这里插入图片描述

单链表-删除节点

找到要删除节点的前置节点p

用指针q记录要删除的节点

通过改变p的后继节点实现删除

释放删除节点的空间

图示:

在这里插入图片描述

int deleteNode(Node *L, int pos, ElemType *e)
{
    Node *p = L; //用于保存要删除的位置的前一个节点
    int i = 0;
    while(i < pos-1) //找到要删除的位置
    {
        p = p->next; //指向下一个节点
        i++;
        if (p == NULL) //若为空
        {
            printf("Position is invalid\n"); //位置无效
            return 0;
        }
        
        Node *q = p->next; //要删除的节点
        *e = q->data; //保存删除的节点的值
        p->next = q->next; //前一个节点指向下一个节点
        free(q); //释放内存
        return 1;
    }
}

单链表-获取链表长度

int listLength(Node *L)
{
    Node *p = L->next; //指向第一个节点
    int length = 0;
    while(p != NULL) //若不为空
    {
        length++; //长度加1
        p = p->next; //指向下一个节点
    }
    return length;
}

单链表-释放链表

指针 p指向头节点后的第一个节点
判断指针 p 是否指向空节点
如果 p不为空,用指针 q记录指针p的后继节点。
释放指针 p 指向的节点
指针 p和指针 q指向同一个节点,循环上面的操作。

图示:

在这里插入图片描述

int freeList(Node *L)
{
    Node *p = L->next; //指向第一个节点
    Node *q;
    while(p != NULL) //若不为空
    {
        q = p->next; //保存下一个节点
        free(p); //释放内存
        p = q; //指向下一个节点
    }
    L->next = NULL; //头节点指向空
    return 1;
}

单链表-应用

问题1:

在这里插入图片描述

解决方案:

使用双指针(快慢指针)

在这里插入图片描述

代码实现:
//链表应用快慢指针
int findNodeFS(Node *L, int k)
{
    Node *fast = L->next; //快指针
    Node *slow = L->next; //慢指针

    for (int i = 0; i < k; i++)
    {
        fast = fast->next; //快指针先走k步
    }

    while (fast != NULL) //快指针不为空
    {
        fast = fast->next; //快指针走一步
        slow = slow->next; //慢指针走一步
    }
    printf("倒数第%d的节点的值:%d\n", k, slow->data); //输出慢指针的值
    return slow->data; //返回慢指针的值
}
问题2:

在这里插入图片描述

解决方案:

快慢指针。

第一步:先求出两个链表的长度m和n。

第二步:Fast指针指向较长的链表,先走m-n步或n-m步。

第三步:同步移动指针,判断它们是否指向同一个节点

在这里插入图片描述

代码实现:
Node* findIntersectionNode(Node *headA, Node *headB)
{
    if(headA == NULL || headB == NULL) //若有一个为空
    {
        return NULL; //返回空
    }
    Node *p = headA; //指向第一个链表
    int lenA = 0;
    int lenB = 0;

    //计算链表A的长度
    while(p != NULL) //若不为空
    {
        lenA++; //长度加1
        p = p->next; //指向下一个节点
    }

    p = headB; //指向第二个链表
    while (p != NULL) //若不为空
    {
        lenB++; //长度加1
        p = p->next; //指向下一个节点
    }
    
    Node *fast;
    Node *slow;
    int step;
    if(lenA > lenB) //若A的长度大于B的长度
    {
        fast = headA; //快指针指向A
        slow = headB; //慢指针指向B
        step = lenA - lenB; //步数为A的长度减去B的长度
    }
    else
    {
        fast = headB; //快指针指向B
        slow = headA; //慢指针指向A
        step = lenB - lenA; //步数为B的长度减去A的长度
    }

    for (int i = 0; i < step; i++)
    {
        fast = fast->next; //快指针先走step步
    }

    while (fast != NULL && slow != NULL) //若快慢指针都不为空
    {
        if(fast == slow) //若快慢指针相等
        {
            return fast; //返回快指针
        }
        fast = fast->next; //快指针走一步
        slow = slow->next; //慢指针走一步
    }
}

问题3:

在这里插入图片描述

解决方案:

用空间换时间。

由于节点的绝对值|data|≤n,所以可以创建一个n大小的数组,用于判断该节点的数值是否存在,用0或1表示。

如果在链表中存在±15的节点,那么在遍历链表时,第一次遇到±15时,则在数组中,将数组下标为15的值赋值为1,表示之前在链表中出现了绝对值为15的节点。之后遇到绝对值为15的节点,都通过判断数组下标为15的值为1,删除掉后续绝对值为15的链表节点。(比较绕,建议多读几遍。)

在这里插入图片描述

代码实现:
void removeNode(Node *L, int n)
{
    Node *p = L; //指向头节点
    int index = 0;
    int *q = (int *)malloc(sizeof(int)*(n+1)); //分配内存

    //遍历数组
    for(int i = 0; i < n+1; i++)
    {
        *(q + i) = 0; //初始化
    }

    while(p->next != NULL)
    {
        //ads获取绝对值
        index = abs(p->next->data); //获取绝对值
        if(*(q + index) == 0) //若为0
        {
            *(q + index) = 1; //赋值为1
            p = p->next; //指向下一个节点
        }
        else
        {
            Node *temp = p->next; //保存要删除的节点
            p->next = temp->next; //删除节点
            free(temp); //释放内存
        }
    }
    free(q); //释放内存

}
问题4:

在这里插入图片描述

解决方案:

使用三个节点,如图所示,依次将second节点指向first节点,直到second节点为NULL,完成链表反转。

在这里插入图片描述

代码实现:
Node* reverseList(Node *head)
{
    Node *first  = NULL; //指向空
    Node *second = head->next; //指向第一个节点
    Node *third  = NULL; //指向空

    while(second != NULL)
    {
        third = second->next; //保存下一个节点
        second->next = first; //第一个节点指向空
        first = second; //第一个节点指向第二个节点
        second = third; //第二个节点指向第三个节点
    }
    Node *hd = initList(); //初始化链表
    hd->next = first; //头节点指向第一个节点

    return hd; //返回头节点
}
问题5:

在这里插入图片描述

解决方案:

Fast指针初始指向第一个节点,Slow节点初始指向头节点,Fast每次走两个节点,Slow每次走一个节点,最后当Fast的下一个节点为NULL时,Slow的下一个节点就是中间节点,直接将Slow的下个节点指向下下个节点即可,然后记得释放掉Slow的下个节点的空间。

在这里插入图片描述

代码实现:
int delMiddleNode(Node *head)
{
    Node *fast = head->next; //快指针
    Node *slow = head; //慢指针

    while(fast != NULL && fast->next != NULL) //若快指针不为空
    {
        fast = fast->next->next; //快指针走两步
        slow = slow->next; //慢指针走一步
    }
    Node *q = slow->next; //保存要删除的节点
    slow->next = q->next; //删除节点
    free(q); //释放内存
    return 1;
}

问题6:

在这里插入图片描述

解决方案:

先找到中间节点。

在这里插入图片描述

然后将中间节点的后半段反转后,然后将两段链表互相插入。即:p1->q1->p2->q2。

在这里插入图片描述

代码实现:
void reOrderList(Node *head)
{
    Node *fast = head->next; //快指针
    Node *slow = head; //慢指针
    while(fast != NULL && fast->next != NULL) //若快指针不为空
    {
        fast = fast->next->next; //快指针走两步
        slow = slow->next; //慢指针走一步
    }
    //此时slow->next为中间节点
    //反转后半部分
    Node *first = NULL; //指向空
    Node *second = slow->next; //指向中间节点 后半段的第一个节点
    slow->next = NULL; //中间节点指向空
    Node *third = NULL; //指向空

    while(second != NULL) //若第二个节点不为空
    {
        third = second->next; //保存下一个节点
        second->next = first; //第二个节点指向空
        first = second; //第一个节点指向第二个节点
        second = third; //第二个节点指向第三个节点
    }

    Node *p1 = head->next; //指向第一个节点
    Node *q1 = first; //指向第一个节点
    Node *p2 = NULL; //指向空
    Node *q2 = NULL; //指向空

    while(p1 != NULL && q1 != NULL) //若p1和q1都不为空
    {
        p2 = p1->next; //保存上半段的下一个节点
        q2 = q1->next; //保存下半段的下一个节点
        p1->next = q1; //上半段的第一个节点指向下半段的第一个节点
        q1->next = p2; //下半段的第一个节点指向上半段的第二个节点
        p1 = p2; //指向上半段的下一个节点
        q1 = q2; //指向下半段的下一个节点
    }
    return 1;
    
}

单链表-单向循环链表

请添加图片描述

例题:

判断链表是否有环。

在这里插入图片描述

解决方案:

快指针走两步,慢指针走一步

在这里插入图片描述

代码实现:

//判断链表是否有环
 int isCyscle(Node *head)
 {
    Node *fast = head; //快指针
    Node *slow = head; //慢指针

    while (fast != NULL && fast->next != NULL) //若快指针不为空
    {
        fast = fast->next->next; //快指针走两步
        slow = slow->next; //慢指针走一步
        if(fast == slow) //若快慢指针相等
        {
            return 1; //返回1
        }
    }
    
 }

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

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

相关文章

ASP.NET Core 如何使用 C# 向端点发出 POST 请求

使用 C#&#xff0c;将 JSON POST 到 REST API 端点&#xff1b;如何从 REST API 接收 JSON 数据。 本文需要 ASP .NET Core&#xff0c;并兼容 .NET Core 3.1、.NET 6和.NET 8。 要从端点获取数据&#xff0c;请参阅本文。 使用 . 将 JSON 数据发布到端点非常容易HttpClien…

DeepSeek模型R1服务器繁忙,怎么解决?

在当今科技飞速发展的时代&#xff0c;人工智能领域不断涌现出令人瞩目的创新成果&#xff0c;其中DeepSeek模型无疑成为了众多关注焦点。它凭借着先进的技术和卓越的性能&#xff0c;在行业内掀起了一股热潮&#xff0c;吸引了无数目光。然而&#xff0c;如同许多前沿技术在发…

GlusterFS 深度洞察:从架构原理到案例实践的全面解读(上)

文章目录 一.GlusterFS简介二.GlusterFS原理架构三.适用场景四.Glusterfs与其他存储产品对比五.部署GlusterFS集群六. 使用heketi将glusterfs接入k8s作为后端存储 一.GlusterFS简介 GlusterFS是一个免费的开源分布式文件系统&#xff0c;具有无中心节点、堆栈式设计、全局统一…

更新无忧:用 Docker 数据卷确保 Open WebUI 数据持久化

在使用 Docker 部署 Open WebUI 时&#xff0c;如何在更新容器的同时确保数据不丢失&#xff0c;始终是工程师们关注的焦点。每次拉取新版镜像、停止并重启容器时&#xff0c;如果没有正确挂载数据卷&#xff0c;配置和数据库数据极易流失&#xff0c;给生产环境带来不必要的麻…

zyNo.22

常见Web漏洞解析 命令执行漏洞 1.Bash与CMD常用命令 &#xff08;1&#xff09;Bash 读取文件&#xff1a;最常见的命令cat flag 在 Bash 中&#xff0c;cat 以及的tac、nl、more、head、less、tail、od、pr 均为文件读取相关命令&#xff0c;它们的区别如下&#xff1a; …

idea如何使用AI编程提升效率-在IntelliJ IDEA 中安装 GitHub Copilot 插件的步骤-卓伊凡

idea如何使用AI编程提升效率-在IntelliJ IDEA 中安装 GitHub Copilot 插件的步骤-卓伊凡 问题 idea编译器 安装copilot AI工具 实际操作 在 IntelliJ IDEA 中安装 GitHub Copilot 插件的步骤如下&#xff1a; 打开 IntelliJ IDEA&#xff1a; 打开你的 IntelliJ IDEA 应用…

Unity 接入Tripo 文生模型,图生模型

官方网站&#xff1a;https://www.tripo3d.ai/app/home自行注册账号并且登陆下载Unity插件&#xff1a;https://cdn-web.tripo3d.ai/plugin/tripo-unity.zip申请apikey&#xff1a; https://platform.tripo3d.ai/api-keys使用&#xff08;后续过程就按照第二步下载的插件里面的…

WPS计算机二级•文档的文本样式与编号

听说这是目录哦 标题级别❤️新建文本样式 快速套用格式&#x1fa77;设置标题样式 自定义设置多级编号&#x1f9e1;使用自动编号&#x1f49b;取消自动编号&#x1f49a;设置 页面边框&#x1f499;添加水印&#x1fa75;排版技巧怎么分栏&#x1f49c;添加空白下划线&#x…

外部中断实验 #STM32F407

外部中断实验 此实验将外部中断配置为按键输入&#xff0c;通过按键输入触发外部中断&#xff0c;在外部中断里面实施相应的处理&#xff0c;具体功能&#xff1a; 按下KEY0&#xff0c;翻转LED0状态按下KEY1&#xff0c;翻转LED1状态按下KEY2&#xff0c;同时翻转LED0和LED1…

kafka了解-笔记

文章目录 kafka快速上手Kafka介绍Kafka快速上手理解Kafka的集群工作机制Kafka集群的消息流转模型 Kafka客户端小型流转流程客户端工作机制 kafka快速上手 Kafka介绍 MQ的作用 MQ&#xff1a;MessageQueue&#xff0c;消息队列&#xff0c;是一种FIFO先进先出的数据结构&#…

渗透利器:Burp Suite 联动 XRAY 图形化工具.(主动扫描+被动扫描)

Burp Suite 联动 XRAY 图形化工具.&#xff08;主动扫描被动扫描&#xff09; Burp Suite 和 Xray 联合使用&#xff0c;能够将 Burp 的强大流量拦截与修改功能&#xff0c;与 Xray 的高效漏洞检测能力相结合&#xff0c;实现更全面、高效的网络安全测试&#xff0c;同时提升漏…

js中的== 和 ===运算符的比较和区别(面试题)

和 运算符用于比较 JavaScript 值是否相等。 自动转换数据类型&#xff0c;允许不同类型值的比较。 进行严格相等比较&#xff0c;仅在值和数据类型都相同的情况下返回 true。NaN 仅在 比较中与自身相等&#xff0c;而在 比较中不相等。null 和 undefined 仅在 比较中相等。…

通过客户端Chatbox或OpenwebUI访问识别不到本地ollama中的模型等问题的解决

Chatbox和Open WebUI 等无法获取到 Ollama里的模型&#xff0c;主要是由以下原因导致&#xff1a; Ollama 服务未正确暴露给 Docker 容器或客户端模型未正确下载或名称不匹配网络配置或权限问题 排查以上问题的思路首先排查ollama服务是否启动&#xff0c;然后再看端口号 使…

C# 上位机--变量

C# 上位机--变量 在 C# 上位机开发领域&#xff0c;变量是构建程序逻辑的基础元素之一。它就像是一个容器&#xff0c;用于存储各种类型的数据&#xff0c;从简单的数值到复杂的对象。正确理解和使用变量&#xff0c;对于开发出高效、稳定且易于维护的上位机程序至关重要。本文…

【Mastering Vim 2_01】开篇词:在 AI 时代持续深耕底层技术,做长期主义的坚定捍卫者

【最新版《Mastering Vim》封面&#xff0c;涵盖 Vim 9.0 版特性】 文章目录 1 背景&#xff1a;AI 时代的底层技术觉醒2 Vim&#xff1a;一款被严重低估的文本编辑神器3 聊聊 IT 人士的职业病4 进阶之道&#xff1a;构建完整的知识体系5 从 AI 时代的深耕与精进再谈长期主义 1…

【JVM详解二】常量池

一、常量池概述 JVM的常量池主要有以下几种&#xff1a; class文件常量池运行时常量池字符串常量池基本类型包装类常量池 它们相互之间关系大致如下图所示&#xff1a; 每个 class 的字节码文件中都有一个常量池&#xff0c;里面是编译后即知的该 class 会用到的字面量与符号引…

Leetcode - 149双周赛

目录 一、3438. 找到字符串中合法的相邻数字二、3439. 重新安排会议得到最多空余时间 I三、3440. 重新安排会议得到最多空余时间 II四、3441. 变成好标题的最少代价 一、3438. 找到字符串中合法的相邻数字 题目链接 本题有两个条件&#xff1a; 相邻数字互不相同两个数字的的…

蓝桥杯K倍区间(前缀和与差分,取模化简)

输入 5 2 1 2 3 4 5 输出 6 思路&#xff1a;首先由连续子串和可以想用前缀和&#xff0c;由于加减法总和取模和分别取模结果不受影响&#xff0c;所以我们前缀和之后直接取模方便观察性质&#xff0c;本题前缀和&#xff1a;1&#xff0c;3&#xff0c;6&#xff0c;10&#…

CEF132 编译指南 MacOS 篇 - depot_tools 安装与配置 (四)

1. 引言 在 CEF132&#xff08;Chromium Embedded Framework&#xff09;的编译过程中&#xff0c;depot_tools 扮演着举足轻重的角色。这套由 Chromium 项目精心打造的脚本和工具集&#xff0c;专门用于获取、管理和更新 Chromium 及其相关项目&#xff08;包括 CEF&#xff…

Ubuntu 20.04 上安装 qBittorrent

qBittorrent 通过终端安装 系统更新系统升级在 Ubuntu 20.04 上添加 Qbittorent PPA系统更新Qbittorent 安装 Qbittorent 是一个开源且可免费使用的点对点比特流客户端。它体积小&#xff0c;不加载内存盘。众所周知&#xff0c;此应用程序可以在许多操作系统&#xff08;例如…