递归使用及分而治之的思想

1.函数调用在内存中的原理?什么是递归?

  • 首先我们要知道我们程序一步步进行指令时是如何调用函数的?

函数调用时,在堆栈区域要申请一片栈帧,函数的局部变量、参数都分配在栈帧上面;而函数返回时,堆栈上的栈帧就要释放了,可以说栈帧的分配和释放遵循后进先出(数据结构的栈的行为方式)。

  • 递归是什么?简单的来说就是一个函数它自己调用了它自己

  • 如下图所示,每当我们调用函数时就会形成栈帧,而如果我们不知道函数的返回值,func就会一直往下调用它自己,而内存的栈其实很小,所以会形成栈溢出这样的错误。因此,我们在设计递归时要设置一个出口,即能返回给上一个调用的func的值,这样才能逐渐返回并消除栈帧,回到main函数。

2.递归的使用要点

  • 最常见的递归就是n的阶乘。我们知道 n!=n*(n-1)! ,而 (n-1)!=(n-1)*(n-2)! ,其实这就是每次求比它小1的数的阶乘,这就是一个递归。千万记得递归需要出口,1!=1 ,这样才能返回给2的阶乘,3的阶乘,......。

//n的阶乘
#include <cstdio>
int Factorial(int n){
    if(n == 1){
        return 1;
    }
    else {
        return Factorial(n - 1) * n;
    }
}
int main(){
    int n;
    scanf("%d",&n);
    printf("%d\n", Factorial(n));
}
  • 根据我们刚刚的分析我们可以发现递归的两个要点,一是把大问题化成小问题(我们不用真的去求n! ,而是把它化成了求 (n-1)! , (n-2)! , ... ,直到1! ;二是最小问题(往往都是我们很容易算出来的,这里 1!=1 我们都知道)。

3.递归例题

例题1 汉诺塔问题

题目描述:

约 19世纪末,在欧洲的商店中出售一种智力玩具:在一块铜板上有三根杆,最左边的杆自上而下、由小到大顺宇串 者由 64 个网盘构成的塔。日的是将最左边杆上的园猛全部移到右边的杆上,条件是一次只能移动一个圆盘,并且不允许大网盘放在小圆盘的上面。现在我们改变这个游戏的玩法:不允许直接从最左(右)边移动到最右(左)边(每次移动一定是移到中间杆或从中间杆移出),也不允许大圆盘放到小國盘的上面。Daisy 已经做过原来的汉诺塔问题和汉诺塔山问题,但碰到这个问题时,她想了很久都无法解决。请你帮助她。现在有N个圆盘,她至少需要多少次移动才能把这些圆盘从最左边移到最右边?

输入:

包含多组数据,每次输入一个N值 (1≤N≤35)。

输出:

对于每组数据,输出移动最小的次数。

题目分析:

(1)汉诺塔问题也是最经典的递归问题,1个2个3个我们可以通过自己打草稿移动得出结果,而一旦这个圆盘数N十分大时,我们是无法计算得出的。因此,对于这种大问题,我们就可以把它化为小问题,进行递归。

(2)如下图所示,我们可以把上面n-1块小的看作一个整体,这就类似于移动两块圆盘(第n块和那n-1块),然后问题就转化成了怎么移这n-1块,也就是把大问题化成了小问题,依此类推逐渐化小,最小问题即是怎么移1块圆盘,显然是移动两次。

(3)大问题化小问题:F(n) = 3*F(n-1)+2

(4)最小问题:F(1) = 2

代码示例:

//汉诺塔问题(递归:大问题化小问题,n-->n-1)
#include <cstdio>
long long hanoi(int n){
    if(n == 1){
        return 2;
    }
    else{
        return 3*hanoi(n-1) + 2;
    }
}
int main(){
    int n;
    while (scanf("%d",&n) != EOF){
        printf("%lld\n", hanoi(n));
    }
}

4.什么是分治法?

  • 分而治之的思想就是运用到递归身上的。

(1)分(devide):大规模问题分解成若干个相似的小问题

(2)治(conquer):解决边界问题

(3)合:合并小问题的解决方案,得到大问题的解决

例题2 Fibonacci (上海交通大学复试上机题)

描述

The Fibonacci Numbers{0,1,1,2,3,5,8,13,21,34,55...} are defined by the recurrence: F0=0 F1=1 Fn=Fn-1+Fn-2,n>=2 Write a program to calculate the Fibonacci Numbers.

输入描述:

Each case contains a number n and you are expected to calculate Fn.(0<=n<=30) 。

输出描述:

For each case, print a number Fn on a separate line,which means the nth Fibonacci Number.

输入:

1

输出:

1

题目分析:

如下图,我们知道F(n)=F(n-1)+F(n-2),那么F(n-1)=F(n-2)+F(n-3);... ;F(2)=F(1)+F(0)。这样逐渐分解,我们会发现最小问题即是F(0)=0和F(1)=1。

代码示例:

//斐波那契数列
#include <cstdio>
int Fibonacci(int n){
    if(n == 1){
        return 1;
    }
    else if(n == 0){
        return 0;
    }
    else{
        return Fibonacci(n-1) + Fibonacci(n-2);
    }
}
int main(){
    int n;
    while(scanf("%d",&n) != EOF){
        printf("%d\n", Fibonacci(n));
    }
}

例题3 二叉树 (北京大学复试上机题 )

如上所示,由正整数1,2,3……组成了一颗特殊二叉树。我们已知这个二叉树的最后一个结点是n。现在的问题是,结点m所在的子树中一共包括多少个结点。 比如,n = 12,m = 3那么上图中的结点13,14,15以及后面的结点都是不存在的,结点m所在子树中包括的结点有3,6,7,12,因此结点m的所在子树中共有4个结点。

输入描述:

输入数据包括多行,每行给出一组测试数据,包括两个整数m,n (1 <= m <= n <= 1000000000)。

输出描述:

对于每一组测试数据,输出一行,该行包含一个整数,给出结点m所在子树中包括的结点的数目。

输入:

3 12

0 0

输出:

4

题目分析

(1)通过下图可以发现,结点3所在子树中包括的结点数目=3的左子树的结点数+3的右子树的结点数+1(它自己),也就分解成了如何求它的左子树结点数和右子树结点数,这样又变成了求子树结点的问题,就可以一直递归下去(注意:序号为m的左孩子序号为2m,右孩子序号为2m+1)。

(2)那递归出口是什么呢?不妨想一想最小问题,也就是最小结点数目,显然就是0个,即左子树右子树和它自己都不存在,要这种情况发生就要该结点序号是大于整棵树节点的(m>n),即该结点不存在。

代码实例:

//二叉树
#include <cstdio>
int tree(int m, int n){
    if(m > n){
        return 0;
    }
    else{
        return 1+tree(2*m,n)+tree(2*m+1,n);
    }
}
int main(){
    int a = 1;
    int m,n;//起始的编号与树的规模
    while (scanf("%d%d", &m,&n) != EOF){
        if(m == 0){
            break;
        }
        printf("%d\n",tree(m,n));
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

~許諾~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值