线性链表 反转 -(递归与非递归算法)_20230420

news2024/11/15 3:34:03

线性链表 反转 -(递归与非递归算法)_20230420

  1. 前言

线性链表反转是非常有趣的算法,它可以采用多种方式实现,比较简洁的方法是递归反转;传统的方式是利用迭代反转,设定三个变量,采用类似滚动数组的方式,实现线性表的反转;利用线性表头部插入方法也不失为一种直观有效的方式,这种情况下需要重新建立线性链表,在遍历目标链表的同时,不断把元素插入新的线性链表的头部,从而实现反转;最后的方式是就地反转,不用设定新的线性链表,在本身线性链表的基础之上,实现元素的反转,这种方式与头插法类似,过程中需要至少三个变量保存链表的头部,待处理结点和链表的尾部。

具体看一个实际例子。假定给到下图的线性链表,要求通过合适的算法,实现线性链表的反转。反转前链表结构,

在这里插入图片描述

反转的过程本质上不是重新建立结点的过程,而是重新链接其next结点以及设定新的头结点的过程,这个过程中结点地址和内容本身不会发生变化,发生变化的是结点中的next指针域和头结点的位置。通过一系列的算法操作,重新建立next链接后的线性链表实现了反转。

在这里插入图片描述

  1. 线性表反转不同算法实现

2.1.线性链表建立

递归算法之前,需要先建立简单的线性链表,线性链表一般由两部分沟通,两个域分别包含数据和指向下一结点的指针。Value域存放数据对象,next域存放指针,指针指向下一个结点。

链表的建立通过函数build_linked_list实现,具体模式采用尾部插入方法,如果链表为空,那么就作为链表的头结点,否则通过循环寻找需要插入位置的前置结点,然后把前置结点的next指向待插入结点即可(p->next = new_ptr)。

在这里插入图片描述

建立链表的具体算法实现,

typedef struct Link_Node
{
    int value;
    struct Link_Node * next;
}Link_Node, *Linked_Elem;

void make_node(Linked_Elem *node, int val)
{
    *node=(Linked_Elem)malloc(sizeof(Link_Node));
    (*node)->value=val;
    (*node)->next=NULL;
}

void build_linked_list(Linked_Elem *list, int val)
{
    Linked_Elem p;
    Linked_Elem new_ptr;

    make_node(&new_ptr, val);

    if(*list==NULL)
    {
        *list=new_ptr; 
    }
    else
    {
        p = *list;

        while (p->next != NULL)
        {
            p = p->next;
        }

        p->next = new_ptr;
    }

    return;
}

2.2 递归算法

首先介绍简洁而优雅的递归算法,递归算法秉承”大事化小,小事化了“的算法理念,最终把问题分而治之,递归的关键是找到子问题,那么寻找线性表反转的子问题就成为解决问题的核心与关键。递归的子问题可以通过迭代,不断缩小线性链表的元素规模。

通过不断向后移动线性表中的元素位置,达到缩小线性表规模的目的。当子问题中仅包含单个元素或原问题本身为空集,此时就构成递归算法的base返回基础的情况。

在这里插入图片描述

当问题规模缩小满足递归出口条件时候,函数就进入出栈操作流程。

递归入栈过程,

在这里插入图片描述

递归出栈过程,

递归的出栈后,实际上仅包含两个操作语句,首先是head->next->next=head,此时对head的next域的next域进行赋值,赋值到其本身,它形成一个环,然后利用语句head->next=NULL, 隔断环的结构。随着不断出栈,那么就形成了从后一元素指向前一元素的线性链表。

在这里插入图片描述

递归过程的难点之一是如何保存反转后链表的头指针,这就需要用到递归扩展(propagation)的基本概念,简单做法就是,在第一次出栈之前,逐步递归返回第一次出栈的元素,也即是相同的首元素不断出栈,不断把值传递给下一个栈,通过return 不断返回当前栈里面指针,给到下一个栈。

在这里插入图片描述

具体的代码实现,

Linked_Elem reverse_linked_list_recursion(Linked_Elem head)
{
    Linked_Elem p;

    if(head==NULL || head->next==NULL)
    {
        return head; //keep head by each frame stack
    }
    else
    {
        p=reverse_linked_list_recursion(head->next); //keep returned pointer
        head->next->next=head;//operation on the parameters of function
        head->next=NULL;//operation on the parameters of function
        return p; //return head by each frame stack
    }
}

2.3 迭代方法

迭代方法采用从线性表头至尾部的的遍历,其本质是对两个结点之间的连接关系进行反转,从最简单的两个元素线性表分析,我们可以看到,只需要把后一个线性表的next指针指向前一个结点,前一个结点的next指针域置空后就满足反转的要求。

在这里插入图片描述

如果线性链表中的元素超过两个,那么问题就来了,邻接元素的next域指向前一个元素之后,就无法再遍历后续的元素。这时候滚动数组的概念应运而生。

具体看实际例子,设定指针start, end 和temp, 每次对start 和end的指针关系进行倒置,同时用temp指针保存倒置之前的end下一个结点,然后这三个结点指针整体往前移动1位,重复上述操作。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码实现

Linked_Elem reverse_linked_list_iteration(Linked_Elem head)
{
    Linked_Elem start;
    Linked_Elem end;
    Linked_Elem temp;

    start=head;
    end=start->next;
    start->next=NULL;

    while(end)
    {
        temp=end->next;
        end->next=start;
        start=end;
        end=temp;        
    }

    return start;
}

2.3 头部插入法

头插法和尾插法实际上是建立线性链表的两种常规的方式,由于题目要求倒置,所以需要从头到尾在线性遍历的同时采用头部插入法,如果采用尾插法,那么就相当于复制一个相同的线性表。

头部插入法的具体实现为,建立一个临时头部结点,同时开始遍历原线性链表,先遍历后插入,最后返回临时头部结点的next域。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码实现

Linked_Elem reverse_linked_list_head_insertion(Linked_Elem head)
{
    Linked_Elem temp_head;
    Linked_Elem p;
    Linked_Elem q;

    temp_head=(Linked_Elem)malloc(sizeof(Link_Node));
    temp_head->next=NULL;
    p=head;

    while(p)
    {
        q=p->next;
        p->next=temp_head->next;
        temp_head->next=p;
        p=q;
    }

    return temp_head->next;
    
}

2.4 原线性表就地反转法(in-place reversal)

对原有线性表进行反转,无需建立额外线性表,反转完成后,原来线性表当中的自动形成一个线性反转链表。这个方法的巧妙之处在于,它通过参数中的head指针,以及额外的start和end指针就可以实现就地反转,可以形象称之为“就地翻筋斗”。

以具体实现流程介绍(部分),它实际上先隔离end结点,用start->next指针进行保存,实际上start->next指针在程序执行过程中它的位置保持不变,主要作用是记录下一个待处理的结点。end->next=head表明,重新进行反转链接的操作,而后更新head的位置,指向新的end, 最后把end移动到新的待处理的结点,这时候一个循环周期完成,直至迭代直至所有结点遍历结束。

在这里插入图片描述

在这里插入图片描述

代码实现

Linked_Elem reverse_linked_list_in_place(Linked_Elem head)
{
    //It is hard in understanding the reverse in place
    Linked_Elem start;
    Linked_Elem end;

    start=head;
    end=start->next;

    while(end)
    {
        start->next=end->next; // Isolate the end and keep its next in start's next
        end->next=head; //link end with old head
        head=end;  // move old head to new head(end pointer)
        end=start->next; // move end to next to be processed target
    }

    return head;
}

  1. 小结

通过线性链表反转练习,复习了线性链表的基本结构,同时通过递归和非递归算法的对比,体现出递归模式的优雅和代码的简洁,本次递归过程中学习了返回值的propagation 模式,这种模式就是通过栈接力,直至把最初的值传递给最后出栈的函数。

参考资料:

《数据结构》清华大学,严蔚敏

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

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

相关文章

React Hooks生命周期

文章目录 前言一、类组件的生命周期1、什么是类组件2、生命周期钩子 二、函数组件生命周期1、什么是函数组件2、模拟类组件生命周期钩子 三、为什么要使用生命周期1、我们能在钩子里面干什么2、PureComponent和React.memo区别 总结 前言 最近在写react项目,所以一直…

【Java网络编程】Socket套接字

哈喽,大家好~我是你们的老朋友: 保护小周ღ,本期为大家带来的是网络编程的前提概念 Socket 套接字,操作系统提供Socket 用于封装底层的协议细节和通信逻辑,使应用程序可以通过简单直观的API与网络进行交互。所以客观的…

在poetry虚拟环境下打包exe

本博客介绍了在poetry虚拟环境下打包exe的流程,包含两个部分 打包的基本流程打包过程中遇到的问题 打包的基本流程 copy打包工具到本地,(share:\公用共享\芯片部\乔羽\img_generate\系统部提供的打包exe工具) 用poetry搭建虚拟环境 在打包…

微积分入门

文章目录 前言初期积分微分微积分问题 后期极限 ε \varepsilon ε- δ \delta δ极限勒贝格积分 结语 前言 微积分总共走过了两个时期。首先是牛顿和莱布尼茨利用无穷小量定义微分和积分,并且发现了微分和积分的关系,这是第一个时期,这时的…

如何在元宇宙中促进品牌增长:消费者喜好的热点调查

欢迎来到Hubbleverse 🌍 关注我们 关注宇宙新鲜事 📌 预计阅读时长:5分钟 本文仅代表作者个人观点,不代表平台意见,不构成投资建议。 音乐和旅游是用户被元宇宙虚拟体验所吸引的前两个领域。根据Reach 3 Insights的…

ArrayList 的特点及优缺点

前面讲过,数组有很多缺点且使用不太方便,但是我们存储数据的时候很多情况又不得不使用它,那么有没有对数组封装一下的类,让我们更方便呢?答案是有的,他就是 ArrayList,他是一个基于数组的集合&a…

Ceph入门到精通- storcli安装

storcli 是LSI公司官方提供的Raid卡管理工具,storcli已经基本代替了megacli,是一款比较简单易用的小工具。将命令写成一个个的小脚本,会将使用变得更方便。 安装简单,Windows系统下解压出来以后可以直接运行。 Linux系统默认位置…

CGI, FastCGI, WSGI, uWSGI, uwsgi一文搞懂

1. CGI # 1、通用网关接口(Common Gateway Interface/CGI)是一种重要的互联网技术,可以让一个客户端,从网页浏览器向执行在网络服务器上的程序请求数据。CGI描述了服务器和请求处理程序之间传输数据的一种标准。 # 2、CGI程序可以…

今儿咱就看看redis的淘汰策略你知道多少

一、前言 Redis在我们日常开发中是经常用到的,Redis也是功能非常强大,可以进行缓存,还会有一些排行榜、点赞、消息队列、购物车等等;当然还有分布式锁Redisson,我们使用肯定少不了集群!小编最近学习到一些…

AI-ISP:手机相机是如何将风景变成图片的?

文章目录 前言1. ISP芯片2. Sensor知识3. RAW数据4. ISP Pipeline5. AI-ISP结束语 前言 本篇文章只干一件事:AI-ISP:手机相机是如何将风景变成图片的? 1. ISP芯片 一颗小小的SoC芯片(10mm x 10mm)上集成了一百多亿个晶体管(Kirin 9000有153亿…

【pandas】Python读取DataFrame的某行或某列

行索引、列索引、loc和iloc import pandas as pd import numpy as np # 准备数据 df pd.DataFrame(np.arange(12).reshape(3,4),indexlist("abc"),columnslist("WXYZ"))行索引(index):对应最左边那一竖列 列索引(columns):对应最…

建筑数据破解JS逆向爬虫

建筑数据破解JS逆向爬虫 地址:https://jzsc.mohurd.gov.cn/data/project GitHub地址:https://github.com/NearHuiwen/JzscCrawler RequestsPyExecJS JS文件:req_aes.js 目标 抓包,抓取建筑市场数据(注:用于学习&am…

multi-scale training多尺度训练

文章目录 背景参考1:[输入不同长度的向量,输出相同长度](https://www.zhihu.com/question/569406523/answer/2780168200):参考2:[多种尺寸的图像数据训练没有全连接层的卷积神经网络模型](https://www.zhihu.com/question/533481647)参考3&am…

展开说说:Adobe XD 哪个版本好用?

工具还是得顺着自己的习惯才是最好 即时设计 - 可实时协作的专业 UI 设计工具即时设计是一款支持在线协作的专业级 UI 设计工具,支持 Sketch、Figma、XD 格式导入,海量优质设计资源即拿即用。支持创建交互原型、获取设计标注,为产设研团队提…

如何快速将PDF文件转换为Word文档

PDF文件是一个广泛使用的电子文档格式,其被广泛应用于各种领域,包括教育、商业和政府。虽然PDF文件非常实用,但有时你需要将其转换为Word文档,以便更方便地编辑和处理。以下是几种快速将PDF文件转换为Word文档的方法。 1. 使用在…

Linux Shell 实现一键部署http+用户名密码登录

Apache 前言 Apache(音译为阿帕奇)是世界使用排名第一的Web服务器软件。它可以运行在几乎所有广泛使用的计算机平台上,由于其跨平台和安全性被广泛使用,是最流行的Web服务器端软件之一。它快速、可靠并且可通过简单的API扩充,将Perl/Python等…

【python】采集天气数据并作可视化,怀念一下之前得好天气

前言 这几天,长沙得天气突然爆冷,每天上班跟渡劫一样 生怕一不小心,风就把伞吹跑了,人湿点无所谓,但是我得伞不能有事 现在得我无比怀念之前得好天气,今天我就来采集一下天气数据并作个可视化怀念一下它…

C# 特性(Attribute)

一、特性(Attribute)定义 特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。 特性使用中括号…

ArduPilot之开源代码框架

ArduPilot之开源代码框架 1. 系统框架2. 工程框架2.1 工程目录2.2 代码组成2.3 运行流程 4. 硬件传感器总线4.1 I2C4.2 SPI4.3 UART4.4 CAN 5. 软件设计概念6. 总结7. 参考资料 在研读ArduPilot的过程,尝试用一些中文的词汇来描述,可能会造成某些理解上的…

sparksql select后插入自己 报错 Cannot overwrite a path that is also being read from

问题现象 spark.version < 3.0.1 执行下面语句报错: Cannot overwrite a path that is also being read from ... 哪些情况算同时读写自己? 如果读自己和写自己在一个spark stage中,就算同时读写自己. spark.table("tb1")// 其他stransform.write.mode("…