【函数递归】深度剖析之汉诺塔和青蛙跳台阶

函数

  • 函数递归
    • 1)什么是递归?
    • 2) 递归的两个必要条件
    • 3) 我对递归的认知
  • 例1 汉诺塔问题
    • 一般思路
    • 递归解题三部曲
  • 例二 青蛙跳台阶


函数递归

1)什么是递归?

程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用 。
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小

2) 递归的两个必要条件

存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后越来越接近这个限制条件。

3) 我对递归的认知

在我第一次遇到函数递归时,比如求一个10的阶乘,首先想知道10的阶乘,就要知道9的阶乘,要知道9的阶乘,就要知道8的阶乘,反复的调用函数,直到1的阶乘,代码如下;

int fact(int a)
{
     
	if (a <= 1)
		return 1;
	else
		return a * fact(a - 1);
}
int main()
{
     
	int a = 0;
	scanf("%d", &a);
	int ret = fact(a);
	printf("%d", ret);
	return 0;
}

画图让大家理解:

【函数递归】深度剖析之汉诺塔和青蛙跳台阶_第1张图片

实际上就是个反复调用自身的过程,说明它每一级的功能都是一样的,因此我们只需要关注一级递归的解决过程即可。在我解决递归相关问题时,看到了一位博主解决递归的方法,简直让我豁然开朗

这是那位博主的原话

我自己在刚开始解决递归问题的时候,总是会去纠结这一层函数做了什么,它调用自身后的下一层函数又做了什么…然后就会觉得实现一个递归解法十分复杂,根本就无从下手。
相信很多初学者和我一样,这是一个思维误区,一定要走出来。既然递归是一个反复调用自身的过程,这就说明它每一级的功能都是一样的,因此我们只需要关注一级递归的解决过程即可
。。

首先我们关注的条件是
1)整个递归的终止条件。
2)一级递归需要做什么?
3)应该返回给上一级的返回值是什么?
接下来就是解递归题的三部曲
1)找整个递归的终止条件:递归应该在什么时候结束?
2)找返回值:应该给上一级返回什么信息?
3)本级递归应该做什么:在这一级递归中,应该完成什么任务?


例1 汉诺塔问题

有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至C杆:
1) 每次只能移动一个圆盘;
2) 大盘不能叠在小盘上面。

一般思路

1)算法分析

第一次移动,要把A柱子上的前n-1个移动到B柱子上;(1)
第二次移动,直接把A柱子上的最后一个移动到C柱子上;(2)
第三次移动,把B柱子上的n-1个柱子通过柱子A移动到柱子C上。(3)
【函数递归】深度剖析之汉诺塔和青蛙跳台阶_第2张图片

现在我们就可以理解这个移动步骤次数
(1)n == 1

第1次 1号盘 A---->C sum = 1 次

(2) n == 2

第1次 1号盘 A---->B
第2次 2号盘 A---->C
第3次 1号盘 B---->C sum = 3 次

3)n == 3

第1次 1号盘 A---->C
第2次 2号盘 A---->B
第3次 1号盘 C---->B
第4次 3号盘 A---->C
第5次 1号盘 B---->A
第6次 2号盘 B---->C
第7次 1号盘 A---->C sum = 7 次

不难发现规律:

1个圆盘的次数 2的1次方减1
2个圆盘的次数 2的2次方减1
3个圆盘的次数 2的3次方减1
。 。 。 。 。
n个圆盘的次数 2的n次方减1

故:移动次数为:2^n - 1


这样我们就可以把这个算法简化为三个步骤;

(1) 把n-1个盘子由A 移到 B;
 (2) 把第n个盘子由 A移到 C;
  (3) 把n-1个盘子由B 移到 C;
代码如下:

#include

void move(int n, char A, char B, char C)
{
     
    if (n == 1)
        printf("\t%c->%c\n", A, C);    //圆盘只有一个时,只需将其从A塔移到C塔
        
    else
    {
     
        move(n - 1, A, C, B);            //递归,把A塔上编号1~n-1的圆盘移到B上,以C为辅助塔 
        printf("\t%c->%c\n", A, C);   //把A塔上编号为n的圆盘移到C上
        move(n - 1, B ,A, C);            //递归,把B塔上编号1~n-1的圆盘移到C上,以A为辅助塔
    } 
}

main()
{
     
    int n=0;
    
    printf("请输入要移动的块数:");
    scanf("%d", &n);
    move(n, 'A', 'B', 'C');
}

递归解题三部曲

1)找整个递归的终止条件:递归应该在什么时候结束?当然是A中只有一个圆盘时,这时候只需要把A中圆盘放到C盘。
2)找返回值:应该给上一级返回什么信息?我们的目的是得到移动步骤,返回的是n-1个圆盘(把n-1个圆盘看为一个整体)和最大圆盘的移动步骤,
3)本级递归应该做什么:在这一级递归中,应该完成什么任务? 我们只需要知道n!=1时剩余的n-1个圆盘的移动步骤,也就是上面算法分析中图片的分析,所以返回了本次圆盘移动步骤

【函数递归】深度剖析之汉诺塔和青蛙跳台阶_第3张图片

例二 青蛙跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个n级的台阶总共有多少种跳法?

问题分析
1)当N=1时,只有1种跳法;
2)当N=2时,可以跳2次1层和跳1次2层,也就是2种跳法;
3)如果台阶级数大于2,
重点来了
设跳上n级台阶有f(n)种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上1级或2级台阶。
当为1级台阶: 剩 n-1个台阶,此情况共有 f(n-1)种跳法;
当为2级台阶: 剩 n-2个台阶,此情况共有 f(n-2)种跳法。
f(n)为以上两种情况之和,即 f(n)=f(n-1)+f(n-2)

【函数递归】深度剖析之汉诺塔和青蛙跳台阶_第4张图片

代码如下:

#include
int frog(int n)
{
     
    if (n == 1)
    {
     
        return 1;
    }
    if (n == 2)
    {
     
        return 2;
    }
    return frog(n - 1) + frog(n - 2);
}
int main()
{
     
    int n = 0;
    scanf("%d", &n);
    int ret = frog(n);
    printf("%d\n", ret);
    return 0;
}

问题延伸
如果一次可以跳k层台阶(n>=k)
我们依葫芦画瓢,按照上面的思路
设跳上n级台阶有f(n)种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上1级或2级台阶或3,4,5…k。
当为1级台阶: 剩 n-1个台阶,此情况共有 f(n-1)种跳法;
当为2级台阶: 剩 n-2个台阶,此情况共有 f(n-2)种跳法。
当为3级台阶: 剩 n-3个台阶,此情况共有 f(n-3)种跳法。

当为k级台阶: 剩 n-k个台阶,此情况共有 f(n-k)种跳法。
f(n)为以上k种情况之和,即 f(n)=f(n-1)+f(n-2)+f(n-3)+…+f(n-k)
在这里插入图片描述

题外话
当我第一次看到这道题时,我想到的是高中的一道物理题:大量原子从高能级向低能级跃迁可能的光子频率有几种和青蛙跳台阶差不多,只不过这道题的原子可以跃迁数是n,答案是(n*(n-1))/2,我验算了一下和青蛙答案一样


你可能感兴趣的:(c语言,算法)