C++语言基础--递归函数

对于很多编程初学者来说,递归算法是学习语言的最大障碍之一。可能也有一大部分人知道递归,也能看的懂递归,但在实际做题过程中,却不知道怎么使用

递归的定义

1、很官方的说法
递归,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。
也就是说,递归算法是一种直接或者间接调用自身函数或者方法的算法。
2、递归的核心思想
通俗来说,递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解。
这就是分治的思想
3、递归的自我感觉

以下纯是个人感受,递归可以视为循环的扩展,我们利用计算机的高速运算能力可以使用循环,让计算机重复做某些事情,当这些事情可以封装成函数,且模式一样,就可以使用递归进行解决了。

递归是程序运行时的一种现象,也是解决某些特定问题时较迭代算法来说更自然更优雅的代码组织方式。
C++语言基础--递归函数_第1张图片
4、递归的时间复杂度

递归可以使用递归树或者主定理去求解,需要多做题去掌握。

递归的三大要素

第一要素:明确你这个函数想要干什么。先不管函数里面的代码什么,而是要先明白,你这个函数的功能是什么,要完成什么样的一件事。

第二要素:寻找递归结束条件。我们需要找出当参数为啥时,递归结束,之后直接把结果返回,请注意,这个时候我们必须能根据这个参数的值,能够直接知道函数的结果是什么。

第三要素:找出函数的等价关系式。我们要不断缩小参数的范围,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变。(分治的思想,大规模问题化解成小规模问题,直到化成单一问题进行解决

递归的过程

C++语言基础--递归函数_第2张图片

void foo()
{
    // 自己调用自己就是递归
    foo();
}

我们知道这个函数要是运行起来,除了让你的程序报出一个「堆栈溢出」的错误外,其他什么作用也没有。在某种程度上说,我们的大脑就是一个计算机。
当我们尝试去理解「自己调用自己」这句话时,大脑也会陷入一个无限的递归过程里,然后「轰」的一声「堆栈溢出」了,所以也就无法去理解了。当然老师还会告诉你递归除了「自己调用自己」外,还有很重要的一部分就是在满足条件的时候函数会返回,这样就避免出现无限递归的过程了。所以一个完整的递归函数组成如下:

void foo()
{
    // 返回部分
    if (condition)
    {
        return;
    }
    
    // 递归部分
    foo();
}

例如:递归求斐波那契数列
C++语言基础--递归函数_第3张图片

从上面的步骤我们可以清晰的看到递归算法的第一步是分治,把复杂的大的问题,给拆分成一个一个小问题,直到不能再拆解,通过退出条件retrun,然后再从最小的问题开始解决,只到所有的子问题解决完毕,那么最终的大问题就迎刃而解。

1、把问题分解成更容易解决的子问题集合,比如可以把计算斐波那契数列的第n项问题分解转换成计算第n-1项加上第n-2项这两个子问题
2、 假设我们有一个函数可以应用在所有的子问题上,比如计算斐波那契数列的fib函数
3、基于步骤2的函数,实现如何把子问题的解拼成最终问题的解,这就是递归部分,在计算斐波那契数列的例子里就是fib(n-1) +fib(n-2)部分
4、递归部分确定了,然后再考虑子问题最终简化到到最底层时该返回什么值。
5、上面4步都做好了之后,剩下的就只是毫无条件的相信计算机了……

例二实现一个函数翻转给定的字符串。
假设我们要翻转的字符串是“abcdef”,那么翻转之后的结果应该是“fedcba”。
毫无疑问这个字符串我们是没办法一下子就翻转过来的,那么「分而治之」吧。
我们不能把多个字符一下子就完全翻转过来,但是假如字符串里的字符只有一个呢?
我们翻转起来最方便了,因为什么都不用做。好了,我们可以把”abcdef“分解成”a“和”bcdef“两个子字符串。”bcdef“比”abcdef“短一个字符,理论上也稍微容易点。假设我们有这样的函数能接受一个字符串,然后像变戏法一样就能返回一个翻转后的字符串,如下:

 string revertString(const string& str)
{
    // str = 'abcdef'
    // str[0] = 'a'
    // str.substr(1) = 'bcdef'
    return revertString(str.substr(1)) + str[0];
}

当然现在这个函数还不能直接交给计算机去执行,因为我们传给revertString的参数字符串在不断的缩短,当缩短到一个字符或者一个字符也没有时,我们就没办法再继续缩短了,我们要把这些情况也处理掉才行:

string revertString(const string& str)
{
    if (str.length() <= 1) {
        return str;
    }
    // str = 'abcdef'
    // str[0] = 'a'
    // str.substr(1) = 'bcdef'
    return revertString(str.substr(1)) + str[0];
}

好了,这样的一个函数就可以交给计算机去执行了,然后剩下的事情就是「见证奇迹」的时刻了。以上的思维过程可以应用于绝大多数递归算法上。

课后练习很重要哦

1、利用递归求N!(N的阶乘)
2、上台阶
3、最大公约数GCD

三道练习题目视频讲解地址
1、洛谷P1010-2的幂次方

2的幂次方P1010

2、洛谷P5461-赦免战俘

赦免战俘-洛谷P5461

3、洛谷T218202-农场周围的路

农场周围的路--洛谷T218202

你可能感兴趣的:(C++语言基础,信息学奥赛初赛,算法,编程语言,csp)