递归
递归的基本思想是某个函数直接或者间接地调用自身,这样原问题的 求解就转换为了许多性质相同但是规模更小的子问题。
求解时只需要关注如何把原问题划分成符合条件的子问题,而不需要 过分关注这个子问题是如何被解决的。
递归代码最重要的两个特征:结束条件和自我调用。 – 自我调用是在解决子问题 – 结束条件则定义了最简子问题的答案。
– 你今年几岁?答:去年的岁数加一岁,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;
}