深入浅出看递归

本文深入浅出地解析了递归的基本概念及其在算法设计中的应用,通过实例讲解了递归如何解决复杂问题,如斐波那契数列、阶乘计算、回文串判断及全排列输出。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本来以为可以不用写这一篇文章的,奈何最近学弟学妹反映深度优先搜索听不懂,原因可能与递归有关?那就写一篇文章,以我微薄的水平尝试阐述一下递归的伟大思想.

首先看定义: 程序调用自身的编程技巧称为递归

然后让我们用一个图,抽象出程序执行过程可能出现的情况:

其中上图的箭头表示程序语句执行的方向,我们知道正常程序,都是从上到下线性执行的,如上图,而调用函数的程序,它会跳转到另一个函数中去执行,结束后会回到断点处继续执行原程序,总体上还是线性的.那么递归呢? --递归也只是调用函数,只不过是在一个函数内部调用该函数自身,进入一个新的"自我执行"的阶段,而且在这个阶段里面又会遇见一条调用自身的语句,从而开始新一轮的"轮回".但如果设计得当,我们让函数在适当的时候停止调用自身,而是返回,那么这些函数也一定会在一个点不满足递归的条件,从而层层返回,直到最后一个递归函数被返回,进入原程序继续执行到结束的过程

所以我们可以归纳一下递归的几个要素/性质:

1.函数内部由自我调用语句;

2.必须存在递归出口;

如此,我们可以写一个基础的递归通式:

void fun()
{
    if(递归出口 == true)
        return;
    fun();
}

上面的代码虽然简单,却反映了递归的两个性质,并非所有的递归函数都是这样写,但是所有的递归函数必定满足上面的性质.

我们来看几个简单的例子:

1.递归求斐波那契数列

int fib(int x)
{
    if(x == 1 || x == 2)    return 1;
    return fib(x-1) + fib(x-2);
}

2.递归求阶乘

int factorial(int x)
{
    if(x == 0 || x == 1)  return 1;
    return x*factorial(x-1);
}

以上都是我们熟悉的递归,因为斐波那契数列和阶乘在数学上都是递归定义的,所以递归实现非常方便.那为什么递归出口如此小的一个返回值,到最后能计算出非常大的数的阶乘或者斐波那契数列的第很多位呢?原因在于递归的第二个返回值,即if落空后执行的语句,这条语句将反复执行很多次才能执行到if条件满足,此时层层返回,虽然一开始的返回值很小,但是返回的路径很深,导致结果就像滚雪球一样慢慢积累,到最后一个返回结束后,结果就很大了.

下面我们再仿照上面的形式,写一些我们不太熟悉的递归函数:

1.递归判断回文串

bool parlindrome(string str,int l,int r)
{
    if(l == r || r-l == 1)  return 1;
    if(str[l] != str[r])    return 0;
    return parlindrome(str,l+1,r-1);
}

2.输出一些数字的全排列

bool vis[10];

void permutation(int a[],int cnt,int len)
{   //函数第一次执行前,将vis数组全部标记为false;
    if(cnt == len)
    {
        for(int i = 0; i < len; i++)
            cout<<a[i];
        cout<<endl;
        return;
    }
    for(int i = 1; i <= len; i++)
    {
        if(!vis[i])
        {
            vis[i] = 1;
            a[cnt] = i;
            permutation(a,cnt+1,len);
            vis[i] = 0;
        }
    }
}

以上两个例子的递归就不是这么寻常的了,当然,也很简单.

其中1是根据回文串的对称性质,不断缩小范围,直到某个时候不匹配或者达到终点,则返回false或true,否则一直递归下去

2则是使用了深度优先搜索的思想,将一串数字1~len的字典序全排列输出出来,关键在于理解vis[i] = 0的操作,这一步是整个递归的精华所在,其详细原理相信读者耐心观摩细细体会一定能够悟透,如果实在不会,请移步我的另一篇文章--神奇的搜索--深度优先和广度优先

 

下面我们总结一下什么时候可能用到递归:

1.一个问题能够拆分成规模更小的子问题,且子问题的解决方式和该问题完全一致,比如快排的递归(处理左右区间规模更小,但是排序思想和处理整个序列一样),回文串判断的递归(处理n-2的序列规模比n小,但是判断回文的方式一致)

2.深度优先搜索的时候,虽然直接套第1条你会发现可能上面的全排序递归好像不存在子问题?(都是一个一个数字来的),但正是由于深度优先的思想,使得它可以使用递归实现--为什么?因为深度优先是一种"不撞南墙不回头"的策略,撞了南墙回头怎么办?我们需要回溯,而回溯,恰恰是递归所擅长的地方,每个递归的出口都是一个回溯条件!

3.很多优秀的算法的逻辑层面的思想就是自我调用,因此可以用递归来实现,比如匈牙利算法求二分图匹配时的Find函数

4.大概就以上这些吧,其实递归还是很好理解的,实在觉得难可以画画流程图,帮助理解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值