对于很多编程初学者来说,递归算法是学习语言的最大障碍之一。很多人也是半懂不懂,结果学到很深的境地也会因为自己基础不好,导致发展太慢。可能也有一大部分人知道递归,也能看的懂递归,但在实际做题过程中,却不知道怎么使用。今天,我们就来说一说递归算法的使用。什么是递归递归,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。也就是说,递归算法是一种直接或者间接调用自身函数或者方法的算法。

 

通俗来说,递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解。

 

递归的基本原理第一:每一级的函数调用都有自己的变量。第二:每一次函数调用都会有一次返回。第三:递归函数中,位于递归调用前的语句和各级被调用函数具有相同的执行顺序。第四:递归函数中,位于递归调用后的语句的执行顺序和各个被调用函数的顺序相反。第五:虽然每一级递归都有自己的变量,但是函数代码并不会得到复制。递归的三大要素第一要素:明确你这个函数想要干什么。先不管函数里面的代码什么,而是要先明白,你这个函数的功能是什么,要完成什么样的一件事。第二要素:寻找递归结束条件。我们需要找出当参数为啥时,递归结束,之后直接把结果返回,请注意,这个时候我们必须能根据这个参数的值,能够直接知道函数的结果是什么。

 

第三要素:找出函数的等价关系式。我们要不断缩小参数的范围,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变。

 

递归的过程读完这篇文章轻松理解递归算法_C语言具体地说,如果递归函数调用自己,则被调用的函数也将调用自己,这将无限循环下去,除非代码中包含终止调用链的内容。通常的方法将递归调用放在if语句中。例如,void类型的递归函数recurs()的代码如下:

 

读完这篇文章轻松理解递归算法_C语言_02

用文字再现这段代码块的内容:只要if语句为true,每个recurs()调用都将执行statement1,然后再调用recurs(),而不会执行statements2 。当前调用结束后,程序控制权将返回给调用它的recurs(),而该recurs()将执行其statements2部分,然后结束,并将控制权返回给前一个调用,依次类推。递归的使用递归的强大之处在于它允许用户用有限的语句描述无限的对象。因此,在计算机科学中,递归可以被用来描述无限步的运算,尽管描述运算的程序是有限的。这一点是循环不太容易做到的。

 

编写正确的递归算法,一定要有 ”归“ 的步骤,也就是说递归算法,在分解问题到不能再分解的步骤时,要让递归有退出的条件,否则就会陷入死循环,最终导致内存不足引发栈溢出异常。下面,我们通过两个例子来学习一下,递归的使用:方法:
  • 求解目标:把关注点放在要求解的目标上。
  • 关系:找到第n次与第n-1次之间的关系。
  • 初始值:确定第1次返回结果。

1.1 n的阶乘

1的阶乘:1! = 1;
2的阶乘:2! = 2 x 1;

n的阶乘:n! = n x (n-1) x … x 2 x 1.
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#include <iostream>using namespace std;// 下面的函数实现n的阶乘int factorial(int n){ if (n == 1) // 如果n=1,则阶乘为1 { return 1; } else // 如果n!=1, 则阶乘为n乘以(n-1)的阶乘。 { return (n * factorial(n-1)); }}int main(){ int n = 0; cout << "输入数字, 得到它的阶乘:" << endl; // 不断从控制台读取数据,直到读入Ctrl + z为止。 while (cin >> n) { cout << factorial(n) << endl; } return 0;}

1.2 斐波那契数列

斐波那契数列指的是这样一个数列:1, 1, 2, 3, 5, 8, 13, 21, 34 …
递推公式为:
F(1) = 1;
F(2) = 1;
F(n) = F(n-1) + F(n-2).
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#include <iostream>using namespace std;// 本函数实现斐波那契数列int fibonacci(int n){ if (n ==1) return 1; else if (n == 2) return 1; else return (fibonacci(n-1) + fibonacci(n-2));}int main(){ int n = 0; cout << "输入斐波那契数列的序号:" << endl; // 不断从控制台读取数据,直到读入Ctrl + z为止。 while (cin >> n) { cout << fibonacci(n) << endl; } return 0;}

 

2. 模拟连续发生的动作方法:连续动作:搞清楚连续发生的动作是什么。关系:搞清楚不同动作之间的关系。边界条件:搞清楚边界条件。2.1 十进制转二进制这里我使用的方法是:除2取余,逆序排列。给一个十进制的整数,一直除以2取余数,直到商为0。然后将所有的余数逆序排列,即为对应的二进制数。例如:123 == 1111011123 / 2 = 61…余161 / 2 = 30…余130 / 2 = 15…余015 / 2 = 7…余17 / 2 = 3…余13 / 2 = 1…余11 / 2 = 0…余1
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#include <iostream>using namespace std;
// 下面的函数实现十进制转二进制void decimal_to_binary(int n){ if ((n/2) == 0)    {            cout << n;       }       else      {            // 注意下面两行代码不能调换顺序            // 逆序排列余数,生成二进制           decimal_to_binary(n/2);            cout << (n % 2);        }    }int main(){        int n = 0;         cout << "输入十进制数,将其转化为二进制数:" << endl;    // 不断从控制台读取数据,直到读入Ctrl + z为止。         while (cin >> n)          {                  decimal_to_binary(n);                  cout << endl;           }         return 0; }
2.2 汉诺塔问题汉诺塔传说:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。具体问题:有三根相邻的柱子,标号为A, B, C,A柱子上从下到上按金字塔状叠放着n个不同大小的圆盘,要把所有盘子一个一个移动到柱子C上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方,请问要如何移动?

读完这篇文章轻松理解递归算法_C语言_03

解决方法:最简单的情况:如果只有一个圆盘,则直接从A移到C即可,A -> C。最复杂的情况:如果有n个圆盘,则可以分解三步:(1) 将A柱子上的上面n-1个圆盘搬到B柱子上;(2) 再将A柱子上的第n个圆盘移动到C柱子上。(3) 最后将B柱子上的n-1个圆盘移动到C柱子上。如下图所示:

读完这篇文章轻松理解递归算法_C语言_03

读完这篇文章轻松理解递归算法_C语言_03

读完这篇文章轻松理解递归算法_C语言_03

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#include <iostream>using namespace std;void hanoi_tower(int n, char a, char b, char c);void move(int numDisk, char a, char b, char c);int main(){    int n = 0;    cout << "请输入圆盘数量:" << endl;    while (cin >> n){        int count = 0;  // 保存移动次数        char a = 'A', b = 'B', c = 'C';        // 将n个盘子从A经过B移动到C        hanoi_tower(n, a, b, c);        cout << endl;    }        return 0;}// 下面的函数实现汉诺塔问题// 将n个盘子从A经过B移动到Cvoid hanoi_tower(int n, char a, char b, char c){    if (n == 1){        move(1, a, b, c);    }    else {        hanoi_tower(n-1, a, c, b);        move(n, a, b, c);        hanoi_tower(n-1, b, a, c);    }}// 将盘子从A移动到Cvoid move(int numDisk, char a, char b, char c){    cout << a << " -> " << c << endl;}

3. 进行“自动的分析”

方法:

 

先假设,有一个函数能给出答案。

在利用这个函数的前提下,分析如何解决问题。

搞清楚最简单的情况下,答案是什么。

3.1 波兰表达式 (前缀表达式)

波兰表达式描述:

波兰表达式是一种把运算符前置的算术表达式:

 

如 2 + 3 的波兰表达式为 + 2 3

如 (2 + 3) * 4 的波兰表达式为 * + 2 3 4

我们需要编程求解出包含 + - * / 四个运算符的波兰表达式的值。

示例1:

输入:* + 11.0 12.0 + 24.0 35.0

输出:1357.0

示例2:

输入:* / + 12 36 + 1 3 - 15 8

输出:84

 

解决方法:

 

假设有一个函数calculator()可以计算波兰表达式。

分析波兰表达式,可以得到解答步骤。那么在当遇到运算符时,调用函数。如下所示:

‘+’: return calculator() + calculator();

‘-’: return calculator() - calculator();

‘*’: return calculator() * calculator();

‘/’: return calculator() / calculator();

当遇到数值时,调用函数如下:

return atof(str);

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#include <iostream>using namespace std;// 下面函数可以求解波兰表达式double calculator(){    char str[10];    cin >> str;    switch (str[0])    {    case '+':        return calculator() + calculator();    case '-':        return calculator() - calculator();    case '*':        return calculator() * calculator();    case '/':        return calculator() / calculator();    default:        return atof(str);   // 将字符串转化为数值    }}int main(){    cout << calculator();    return 0;}

3.2 放苹果

描述:

把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问有多少种不同的分法?(所有的苹果都一样,所有的盘子都一样。)

注意:5, 1, 1 和 1, 5, 1 是同一种分法,与排列顺序无关。

示例1:

输入:7 3

输出:8

示例2:

输入:100 100

输出:190569292

 

解决方案:

 

假设有个函数count(m, n)可以解决放苹果问题。

分析:因为盘子都是一样的,所以不是排列问题,而是苹果的组合问题。苹果的数量是M,盘子的数量是N。分两种情况讨论:

(1) 当M < N时,多余的盘子都不影响苹果的分法。所以可以得出count(m, n) == count(m, m);

(2) 当M >= N时,又分两种情况:一是有空盘子的情况,则至少有一个空盘子,空盘子去掉不会影响苹果分法,因此count(m, n) ==count(m, n-1)。二是没有空盘子的情况,则每个盘子里面至少有一个苹果,去掉每个盘子里的那一个苹果,不会影响苹果分法,因此count(m, n) == count(m-n, n)。

边界条件:当M <= 1或者N <= 1时,只有一种分法,因此count(m, n) == 1。

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
#include <iostream>using namespace std;// 下面函数能够解决放苹果问题int count(int m, int n){    // 边界条件    // 当M <= 1或者N <= 1时,只有一种分法,因此count(m, n) == 1。    if (m <= 1 || n <= 1)    {        return 1;    }    // 当M < N时,多余的盘子都不影响苹果的分法。    // 所以可以得出count(m, n) == count(m, m);    if (m < n)    {        return count(m, m);    }    // 当M >= N时,又分两种情况:    // 一是有空盘子的情况,则至少有一个空盘子,空盘子去掉不会影响苹果分法,    // 因此count(m, n) == count(m, n-1)。    // 二是没有空盘子的情况,则每个盘子里面至少有一个苹果,去掉每个盘子里的那一个苹果,    // 不会影响苹果分法,因此count(m, n) == count(m-n, n)。    else     {        return (count(m, n-1) + count(m-n, n));    }}int main(){    int m = 0, n = 0;    cin >> m >> n;    cout << count(m, n) << endl;    return 0;}