【数据结构与算法】深入浅出递归和迭代的通用转换思想[通俗易懂]

【数据结构与算法】深入浅出递归和迭代的通用转换思想[通俗易懂]递归和递归的深入浅出一般来说,能用迭代的地方就不要用递归!理论上讲,所有的递归和迭代之间都能相互转换!(一)何为迭代?首先我们来看下面这段简单的代码:intsum(intn){intsum=0;for(inti=1;i<=n;i++)sum+=n;//求解1~n的和returnsum;}从上述例子中,从1一直加到n,每一次的和都

大家好,又见面了,我是你们的朋友全栈君。


深入浅出递归和迭代的通用转换思想


一般来说,能用迭代的地方就不要用递归!理论上讲,所有的递归和迭代之间都能相互转换!

刷题碰到【一天一道LeetCode】#130. Surrounded Regions所以来总结一下递归和迭代。

(一)何为迭代?

首先我们来看下面这段简单的代码:

int sum(int n )
{
    int sum =0;
    for(int i = 1 ; i <= n;i++) sum+=n;//求解1~n的和
    return sum;
}

从上述例子中,从1一直加到n,每一次的和都是在上一次的和上加上n,因此,我们不难理解,所谓迭代法(辗转法),就是一种不断用变量的旧值递推新值的过程。

迭代三大步骤:

  1. 确定迭代变量:确定一个直接或间接地不断由旧值推断新值的变量,如sum
  2. 建立迭代关系式:从变量的旧值推断到新值的公式,如f(n) = f(n-1)+n
  3. 对迭代过程进行控制:迭代不可能无限循环下去,需要对何时退出迭代进行控制!如i>n推出循环

(二)何为递归?

还是一样,让我们看看下面这个例子。

int sum(int n )
{
    if(n==1) return 1;
    else return n+sum(n-1);
}

同样是求0~n的和,这段代码是每次在函数体中调用自身函数,1~n的和可以拆分成两个部分,1~n-1的和加上n,因此,递归的思想就是:在函数或子过程的内部,直接或者间接地调用自己的算法,从而把问题转化为规模缩小了的同类问题的子问题,

递归算法的步骤:
1. 确定递归公式,如sum(n) = sum(n-1)+n
2. 确定递归结束条件,如n=1结束递归

(三)递归和迭代,选谁?

举一个简单的例子,求解斐波那契数列。

//1、迭代版本
int fib(int n )
{
    if(n<2) return 1;
    int f0 = 1,f1=1;
    for(int i = 2 ; i < n ; i++){
        int f2= f0+f1;
        f0=f1;f1=f2;
    }
    return f1;
}
//2、递归版本
int fib1(int n)
{
    if (n <= 1) return 1;      
    return fib1(n-1) + fib1(n-2);  
}

在例子中,迭代算法明显没有递归算法简洁,但是迭代算法效率高,运行时间正比于循环次数,而且没有调用函数引起的额外开销。

递归版本的代码很简介清晰,可读性强。但是递归存在一个致命的缺点就是,递归的深度太深会导致堆栈溢出!

我们注意到,每一次调用自身函数的时候,该函数都没有退出,而是继续运行。在函数调用过程中,系统会分配一个堆栈,当递归深度越深,堆栈的占用就越大,造成的后果就是会产生堆栈溢出。

所以,在能够用迭代的地方就不要用递归。这里又有问题呢?递归的思想简单,容易想,那如何才能借助递归的思想写出迭代的算法呢?下面一节就介绍一种通用的转换方式。

(四)递归转成迭代的通用方式

尾递归转换成迭代

尾递归:递归的特殊情况,函数调用出现在函数尾部的递归方式。上述两个例子都输入尾递归。

尾递归可以轻松的转换成迭代方式。这里就不在具体说明了。

非尾递归转换成迭代

非尾递归转换成迭代就必须用到堆栈,简而言之,就是模拟函数调用的堆栈。

还是举一个例子来说明转换方法:

//快速排序的迭代版本
//注:这里的partition函数省略
void QuickSort1(int beg, int end) 
{      
    if (end - beg <= 1) return;      
    int pos = partition(beg, end);      
    QuickSort1(beg, pos);      
    QuickSort1(pos + 1, end);  
} 
//利用堆栈转成成迭代版本
void QuickSort2(int beg, int end) 
{      
    stack<pair<int ,int>> temp_stack;//利用堆栈来保存begin和end的值
    temp_stack.push(pair<int,int>(begin,end));
    while(!temp.empty())//堆栈不为空则继续循环
    {
        pair<int,int>  tmp = temp_stack.top();
        temp_stack.pop();//模拟函数调用,去除栈顶元素,对其进行处理
        if(temp.second - tmp.first > 1)//和递归版本一样,只剩两个数的时候结束递归,否则继续压栈
        {
            int pos = partition(beg, end);  
            temp_stack.push(pair<int,int>(tmp.first,pos));
            temp_stack.push(pair<int,int>(pos+1,tmp.second));
        }
    }
} 

这里,利用堆栈来存储每一次递归的首尾元素,减少了函数调用带来的额外开销,也避免了系统堆栈的溢出。

当然,上述例子只是一个简单的例子,阐述了一个利用堆栈来完成递归算法转换成迭代算法的思想。

当递归的中间变量增多时,就需要利用更大的数据结构来存储函数调用的中间变量,但思想是不变的。

之所以总结这篇博客,是因为在这篇博文中,用递归会导致堆栈溢出,而转换成迭代版本就可以轻松AC。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/134987.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 【用python编写一个简单的单线程wifi暴力破解工具】

    源代码a.txt:密码文件crack.py:wifi破解模块main.py:主模块scan.py:wifi扫描模块scan.pyimportpywifiimporttime#WiFi扫描模块defwifi_scan():#初始化wifiwifi=pywifi.PyWiFi()#使用第一个无线网卡interface=wifi.interfaces()[0]#开始扫描interface.scan()

    2022年4月9日
    49
  • DeviceIOControl实战「建议收藏」

    DeviceIOControl实战「建议收藏」实战DeviceIoControl之一:通过API访问设备驱动程序Q 在NT/2000/XP中,我想用VC编写应用程序访问硬件设备,如获取磁盘参数、读写绝对扇区数据、测试光驱实际速度等,该从哪里入手呢?A 在NT/2000/XP中,应用程序可以通过API函数DeviceIoControl来实现对设备的访问—获取信息,发送命令,交换数据等。利用该接口函数向指定的设备驱动发送正确

    2022年9月7日
    2
  • Debug.WriteLine输出调试信息[通俗易懂]

    Debug.WriteLine输出调试信息转载于:https://www.cnblogs.com/xuyuchen/p/8283023.html

    2022年4月10日
    101
  • MySQL 事务隔离级别[通俗易懂]

    MySQL 事务隔离级别[通俗易懂]1.理论MySQL中事务的隔离级别一共分为四种,分别如下: 序列化(SERIALIZABLE) 可重复读(REPEATABLEREAD) 提交读(READCOMMITTED) 未提交读(READUNCOMMITTED) 四种不同的隔离级别含义分别如下: SERIALIZABLE ❝如果隔离级别为序列化,则用户之间通过一个接一个顺序地执行当前的事务,这种隔离级别提供了事务之间最大限度的隔离。 REPEATABLEREAD ❝在可

    2022年10月14日
    2
  • python之sympy库–在线性代数领域的应用

    python之sympy库–在线性代数领域的应用sympy作为相对比较全的数学计算库,其也包含针对线性代数的符号运算部分,本文着重介绍sympy在处理线性代数相关问题时的使用方法,且基本严格结合线性代数教材(同济大学版),便于大家回顾,如果想了解sympy在初等代数或微积分方面的应用,可以看文章《python之sympy库–数学符号计算与绘图必备》。一、矩阵运算1.1创建矩阵创建矩阵是使用sympy处理线性代数问题的起点,以下主要介绍通用创建矩阵的方式以及快速创建特殊矩阵的方式,且一部分主要对应于线性代数教材(同济大学版)的第一章和第二章

    2022年5月14日
    95
  • 01 ORA系列:ORA-00904 标识符无效 invalid identifier

    01 ORA系列:ORA-00904 标识符无效 invalid identifier如果希望对常见的 Oracle 异常 ORA 报错解决方案有系统的了解 请看 ORACLE 系列异常总结 ORA nbsp 转载请说明出处 https blog csdn net baidu article details nbsp 1 字段名称与数据库中关键字冲突修改如下 nbsp 2 多层嵌套查询 内层字段别名使用了双引号错误原因 内层查

    2025年7月22日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号