递归上课笔记

递归

递归的基本思想是某个函数直接或者间接地调用自身,这样原问题的 求解就转换为了许多性质相同但是规模更小的子问题

求解时只需要关注如何把原问题划分成符合条件的子问题,而不需要 过分关注这个子问题是如何被解决的。

递归代码最重要的两个特征:结束条件和自我调用。 – 自我调用是在解决子问题 – 结束条件则定义了最简子问题的答案。

– 你今年几岁?答:去年的岁数加一岁,2008年我出生。

– 你去年几岁?答:前年的岁数加一岁,2008年我出生。

– …

写递归的几个步骤

1、明确函数含义(题目问什么我们设什么)

2、思考对于当前规模的问题,如何把它缩小成规模更小的子问题。 – 假设子问题都已经被正确解决了

3、把求解当前问题转换成求解子问题(自我调用)

4、写结束条件(最简子问题的求解)

计算阶乘

已知阶乘的定义如下:

     – n! = n * (n-1) * (n-2) * … * 3 * 2 * 1

求 n!

     – 思路:把问题分解为与阶乘本身相关的子问题,确定最简子问题为 1!

     – 可以把上面式子记为

#include<iostream>
using namespace std;

int factorial(int n){
    if(n == 1) return 1;
    else return n * factorial(n-1);
}
int main(){
    int k;
    cin >> k;
    cout << factorial(k) <<endl;
    return 0;
}

练习题

编写一个递归函数,计算a^b。

#include<iostream>
#include<bits/stdc++.h>
using namespace std;

int f (int a,int b){
    if(b==1)
        return a;
    else
        return f(a, b - 1) * a;
    }

int main()
{
    int a, b;
    cin >> a >> b;
    cout << f(a, b);
    return 0;
}

斐波那契数列

已知一个数列满足以下要求:

– A[0] = A[1] = 1 – A[n] = A[n-1] + A[n-2]

求数列的第 K 项

#include<iostream>
using namespace std;

int fibo(int n){
    if(n == 0 || n == 1) return 1;
    else return fibo(n-1) + fibo(n-2);
}
int main(){
    int k;
    cin >> k;
    cout << fibo(k) << endl;
    return 0;
}

两个正整数的最大公约数

辗转相除法 – 用 gcd(a, b) 表示 a 和 b 的最大公约数,根据数学知识可知 gcd(a, b) = gcd(b, a % b)。 这个式子实际上给出了求解最大公约数的递归算法。

– 最简子问题:gcd(a, 0) = a

例子:gcd(32, 26) = gcd(26, 6) = gcd(6, 2) = gcd(2, 0) = 2

#include<iostream>
using namespace std;

int gcd(int a, int b) {
    if (b == 0) return a;
    else return gcd(b, a%b);
}
int main(){
    int x, y;
    cin >> x >> y;
    cout << gcd(x, y) << endl;
    return 0;
}

递归实现的优缺点

优点:

– 结构清晰,可读性强

– 增加不同的思维方式,锻炼分析问题的能力  

缺点:

– 函数调用时会浪费一定的空间,每次调用函数时又嵌套调用函数,使得 空间不断被占用堆积而无法释放,若堆积层数太多会导致“内存溢出” (一般称作 栈溢出)。

奶牛吃草

定义一个函数 cnt(n) 来计算 n 头牛的一群最终会分成多少群。

– 每次是否分群的判断是一致的,所以我们只需要实现一次分群的判断, 就可以递归调用让牛群不断的分群。

– a、b的解为:

a = (n - k) / 2 + k //第一群奶牛的数量

b = (n – k) / 2 //第二群奶牛的数量

n – k 必须是 2 的倍数 //不能有一头奶牛被一分为二

且 a 和 b 均大于1

#include<iostream>
using namespace std;
int k;//函数中使用了k,所以把k定义为全局变量
//函数:计算n头牛的一群最终分成多少群
int cnt(int n) {
    //计算 b = (n - k) / 2 时,如果期间 n - k 可以整除以2,并且得到的结果大于0
    //那么就可以分成两群,这两群还可能继续分群下去,递归调用继续分群
    if ((n - k) % 2 == 0 && n - k > 0) return cnt((n - k) / 2) + cnt((n - k) / 2 + k);
    else return 1;
}
int main(){
    int n;
    cin >> n >> k;
    cout << cnt(n) <<endl;
    return 0;
}

汉诺塔问题

现在一根柱子 A 上有若干个大小不一的圆盘,要求大的圆盘不能位于 小的圆盘上方,在限定每次只能移动一个圆盘的要求下,如何把 A 上 的 n 个圆盘都移动到 柱子 B 上?

– 定义 move(n, from, to) 函数,来实现 把编号从小到大的 n 个圆盘从 from 柱子 移动到 to 柱子上。

 

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
int a = 1;
void move(int n, char ks, char fz, char mb){
    if(n==0)
        return;
    move(n - 1, ks, fz, mb);
    cout <<a<<'.'<< "Move " << n << " from " << ks << " to " << mb << endl;
    a++;
    move(n - 1, fz, mb, ks);
}

int main()
{
    int n;
    cin >> n;
    move(n, 'a', 'b', 'c');
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值