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

news/2024/4/28 2:49:35/文章来源:https://blog.csdn.net/Jasonchen1224/article/details/130271279

线性链表 反转 -(递归与非递归算法)_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 pointerhead->next->next=head;//operation on the parameters of functionhead->next=NULL;//operation on the parameters of functionreturn 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 placeLinked_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 nextend->next=head; //link end with old headhead=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.luyixian.cn/news_show_102370.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qt中使用 ui 文件进行界面设计

目录 1、创建 Qt 应用 ​2、项目创建成功 3、直接点击打开 mainwindow.ui 文件 4、随便从左边侧边栏拖拽一个空间到 界面设计区域 5、在右侧边栏右键点击 pushButton 控件,点击转到槽 6、根据实际需要选择对应的信号,我这里方便演示选择 clicked&a…

SpringCloud --- Ribbon负载均衡

一、负载均衡原理 SpringCloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的。 那么我们发出的请求明明是http://userservice/user/1,怎么变成了http://localhost:8081的呢? 二、源码跟踪 为什么我们只输入了service名称就…

【博学谷学习记录】超强总结,用心分享 | 架构师 MinIO学习总结

文章目录 MinIO对象存储的概念计算机数据存储系统-架构模式对象存储的优势常见的对象存储系统/服务(Object Storage Service,OSS) MinIO简介特点高级特性小结 MinIO部署基于 linux Binary 部署 MinIO ServerMinIO数据组织结构MinIO Client**基…

ISO-27145故障诊断说明

ISO-27145故障诊断说明 2.1 27145目录说明 ISO27145-1: 这里边介绍的是一般信息和用例定义; ISO27145-2: 这里边介绍的是与排放相关的通用数据规则,用于查询; ISO27145-3: 这里边主要介绍了支持的服务 12服务 14服务 19服务 22服务 31服务&…

「C/C++」C/C++强制类型转换

博客主页:何曾参静谧的博客 文章专栏:「C/C」C/C学习 目录 相关术语C语言中的强制类型转换C中的强制类型转换static_castdynamic_castreinterpret_castconst_cast 注意事项 相关术语 强制类型转换:是指将一个数据类型强制转换为另一个数据类型…

Python初学小知识(十四):数据分析处理库Pandas

Python初学小知识(十四):数据分析处理库Pandas 十八 Pandas1 文件读取1.1 读取csv1.2 读取txt1.3 读取excel(xlsx) 2 内容读取2.1 读取行2.2 读取列 3 数据处理3.1 加减乘除3.1.1 列 与 元素3.1.2 列 与 列 3.2 最值、…

GoJS Beginner Tutorial #1

1.关系图: gojs部件由一个或多个gojs面板组成,这些面板包含和组织各种gojs图形对象 通常使用go.GraphObject.make创建一个GraphObject,我们通过使用$符号变量缩短了该函数的名称 这个函数的第一个参数,往往是你想要制作的GraphOb…

Centos切换jdk版本

先安装了jdk1.8的版本,需要使用jdk17的版本 1.先安装jdk17,再配置环境变量: vim ~/.bashrc 2.在最后一行添加 ##这个添加的就是路径,一定要和自己jdk安装的路径是一致的 export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 3.然…

docker容器:docker镜像的三种创建方法及dockerfile案例

目录 一、基于现有镜像创建 1、创建启动镜像 2、生成新镜像 二、基于本地模板创建 1、OPENVZ 下载模板 2、导入容器生成镜像 三、基于dockerfile创建 1、dockerfile结构及分层 2、联合文件系统 3、docker镜像加载原理 4、dockerfile操作常用的指令 (1)FROM指令 (…

响应式布局

文章目录 响应式布局概述viewport 视口CSS 常用单位CSS 媒体查询语法直接使用使用style标签使用link引入 自适应布局栅格系统响应式布局案例rem媒体查询 响应式布局 概述 响应式布局是指网站或应用程序可以自适应不同的屏幕尺寸和设备类型,简而言之就是一个网站兼…

Sentinel同时配置fallback和blockHandler的问题

Spring Cloud在使用Sentinel进行服务降级和熔断时,如果同时配置了fallback和blockHandler,则在服务熔断后,抛出的BlockException不会再fallback逻辑中执行,而是在blockHandler逻辑中执行。 首先来看只配置了fallback的情况&#x…

常用的设计模式(单例模式、工厂模式等)

1.单例模式 概述: 在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式. 例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造…

战争教育策略游戏 MiracleGame,开启新阶段重塑生态和玩法

香港 Web3 区块链周刚刚在一片喧嚣中结束。各路大V、KOL 们的 report 都对 GameFi 的前景非常自信。2021-2023年期间,大量资金涌入 GameFi 赛道,GameFi 一旦爆发将会是现象级的出圈事件。 MiracleGame 是一款基于 BNB Chain 构建的英雄和元神主题的战争教…

HNCTF-re部分复现

目录 [HNCTF 2022 WEEK3]Help_Me! [HNCTF 2022 WEEK3]Whats 1n DLL? [HNCTF 2022 WEEK4]ez_maze 这几天在做HNCTF的week3,week4部分,学到了一些不知道的没接触过的东西,所以记录一下 [HNCTF 2022 WEEK3]Help_Me! 题目下载:下…

[自注意力神经网络]Mask Transfiner网络-论文解读

本文为CVPR2022的论文。国际惯例,先贴出原文和源码: 原论文地址https://arxiv.org/pdf/2111.13673.pdf源码地址https://github.com/SysCV/transfiner 一、概述 传统的Two-Stage网络,如Mask R-CNN虽然在实例分割上取得了较好的效果&#xff…

ARM busybox 的移植实战2

一、busybox 源码分析1 1、源码目录梳理 2、整个程序入口的确认 (1) 分析一个程序,不管多庞大还是小,最好的路线都是 按照程序运行时的逻辑顺序来。所以找到一个程序的入口至关重要。 (2) 学 C 语言的时候都知道,程序的主函数 main 函数就是…

【JUC高并发编程】—— 初见JUC

一、JUC 概述 什么是JUC JUC 是 Java并发编程的缩写,指的是 Java.util.concurrent 即Java工具集下的并发编程库 【说白了就是处理线程的工具包】 JUC提供了一套并发编程工具,这些工具是Java 5以后引入的,使得Java开发者可以更加方便地编写…

【系统集成项目管理工程师】项目干系人管理

💥十大知识领域:项目干系人管理 项目干系人管理包括以下 4 个过程: 识别干系人规划干系人管理管理干系人参与控制干系人参与 一、识别干系人 输入工具与技术输出项目章程采购文件事业环境因素组织过程资产组织相关会议专家判断干系人分析干系人登记册 …

ansible自动运维——ansible使用临时命令通过模块来执行任务

大家好,这里是天亮之前ict,本人网络工程大三在读小学生,拥有锐捷的ie和红帽的ce认证。每天更新一个linux进阶的小知识,希望能提高自己的技术的同时,也可以帮助到大家 另外其它专栏请关注: 锐捷数通实验&…

为什么使用了索引,查询还是慢?

🏆今日学习目标: 🍀为什么使用了索引,查询还是慢? ✅创作者:林在闪闪发光 ⏰预计时间:30分钟 🎉个人主页:林在闪闪发光的个人主页 🍁林在闪闪发光的个人社区&…