chapter8-递归与分治

1.递归

递归,指直接或间接地调用自身,解决此类题目的方法见我之前走台阶的笔记,重点有2个,(1)分析问题,归纳出递归公式;(2)递归出口。就像俄罗斯套娃一样,要让递归调用总体朝向递归出口推进。

n的阶乘
//递归-n的阶乘 2024-03-08
#include <iostream>
#include <cstdio>
using namespace std;
long long Factorial(int n) {
if(0 == n) {
return 1;
} else {
return n * Factorial(n - 1);
}
}
int main()
{
int n;
while(cin >> n) {
cout << Factorial(n) << endl;
}
return 0;
}

汉诺塔问题

用递归策略来处理常规的汉诺塔问题,即把左边的从小到大的圆圈移到右边任意一个空的柱子上,如图所示。设把n个圆圈按照限制挪到最右边的空柱子上,需要的总步数为fn,那么现在我们要将最大的圆圈挪到最右边这个柱子上,首先要把n1个圆圈挪到中间的柱子上,这一步需fn-1步骤数。
汉诺塔1.jpg
再把大圆圈挪到最右边的柱子上,需1步骤数,由于此时最右边柱子上的圆圈是最大的,所以不存在限制,可以看成是一个空柱子,**此时再把中间的n1个圆圈挪到右边柱子上,需fn-1步骤数。

分析之后,得到递归公式:fn = 2 * fn-1 + 1
递归出口:f1=1

如果考试的时候你能根据递归公式推出通项表达式,进而总结出公式fn=2n1,那么直接套公式即可,如果不行,建议还是用递归做比较好。

汉诺塔(Hanoi)
//常规汉诺塔问题 2024-03-08
#include <iostream>
#include <cstdio>
using namespace std;
long long Hanoi(int n) {
if(n == 1) {
return 1;
} else {
return 2 * Hanoi(n - 1) + 1;
}
}
int main()
{
int n;
while(cin >> n) {
cout << Hanoi(n) << endl;
}
return 0;
}

HDU OJ 2064 汉诺塔III

汉诺塔III
设把最左边柱子的n个圆圈,经过中间的空柱子,再放到最右边的空柱子,所需要的总步数为fn。那么我们先把n1个圆圈放到最右边的柱子上就需要步骤数fn-1;把最大的圆圈挪到中间的柱子上,需要1步;再把最右边的n1个圆圈从最右边挪到最左边,需要fn-1步;此时把最大的圆圈放到最右边,需要1步。
汉诺塔3.jpg
此时最右边的柱子上只有一个最大的圆圈,不受限制,可以看成是一个空柱子,故此时把最左边的n1个圆圈移到最右边,需要步数fn-1

得到递归公式:fn = 3 * fn-1 + 2
递归出口:考虑基础情况,只有一个大圆圈(n=1)在最左边,把它移到最右边需要2

汉诺塔III(Hanoi)
//递归-汉诺塔III 2024-03-08
#include <iostream>
#include <cstdio>
using namespace std;
long long Hanoi3(int n) {
if(n == 1) {
return 2;
} else {
return 3 * Hanoi3(n - 1) + 2;
}
}
int main()
{
int n;
while(cin >> n) {
cout << Hanoi3(n) << endl;
}
return 0;
}

2.分治

分治策略是另一个非常重要的算法,字面解释就是把一个复杂的问题分成两个或更多个子问题,子问题之间互相独立且与原问题相同或相似。与贪心策略不同,如果子问题仍然不好求解,可以继续再把子问题分成更小的子问题,直到最后的子问题可以简单地直接求解。

由于分治法产生的子问题往往与原问题相同且模式更小,这就为使用递归策略求解提供了条件。在从过程中会导致了递归过程的产生。

分治法的步骤与贪心策略极其相似:
分治法.jpg

求解Fibonacci

Fibonacci数列的求解有三种方法,分别是:
求解Fibonacci.jpg

2.1.1 分治法求解Fibonacci

非常低效,但可以用来理解分治法的精髓,低效的原因是有大量的重复计算,如果我们直接把每一个Fibonacci数列值从小到大保存到一个数组中,那么就是递推法,时间复杂度还不错。

注意,分治法通常会采用分治法进行求解

分治法求Fibonacci
//分治法-Fibonacci 2024-03-08
#include <iostream>
#include <cstdio>
using namespace std;
int Fibonacci(int n) {
if(n == 0 || n == 1) {
return n;
} else {
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
}
int main()
{
int n;
while(cin >> n) {
cout << Fibonacci(n) << endl;
}
return 0;
}

2.1.2 递推法求解Fibonacci

递推法求Fibonacci
//递推法-Fibonacci 2024-03-08
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN = 35;
int Fibo[MAXN];
void Initial()
{
Fibo[0] = 0;
Fibo[1] = 1;
for(int i = 2; i < MAXN; ++i) {
Fibo[i] = Fibo[i-1] + Fibo[i-2];
}
}
int main()
{
Initial();
int n;
while(cin >> n) {
cout << Fibo[n] << endl;
}
return 0;
}

2.1.2 矩阵快速幂求解Fibonacci

fibo公式.jpg
公式推导:
数学归纳法的方式
fibo1.jpg
fibo2.jpg
fibo3.jpg

矩阵快速幂求Fibonacci
//矩阵快速幂-Fibonacci 2024-03-08
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN = 10;
struct Matrix {
int matrix[MAXN][MAXN];
int row, col;
Matrix() {}
Matrix(int r, int c): row(r), col(c) {}
};
Matrix Multiply(Matrix x, Matrix y) {
Matrix answer = Matrix(x.row, y.col);
for(int i = 0; i < answer.row; ++i) {
for(int j = 0; j < answer.col; ++j) {
int sum = 0;
for(int k = 0; k < x.col; ++k) {
sum += x.matrix[i][k] * y.matrix[k][j];
}
answer.matrix[i][j] = sum;
}
}
return answer;
}
Matrix QuickPower(Matrix x, int n) {
Matrix answer = Matrix(x.row, x.row);
for(int i = 0; i < answer.row; ++i) {
for(int j = 0; j < answer.col; ++j) {
if(i == j) {
answer.matrix[i][j] = 1;
} else {
answer.matrix[i][j] = 0;
}
}
}
//快速幂
while(n > 0) {
if(n % 2 == 1) {
answer = Multiply(answer, x);
}
n /= 2;
x = Multiply(x, x);
}
return answer;
}
int main()
{
int n;
Matrix start(2,2);
start.matrix[0][0] = 1;
start.matrix[0][1] = 1;
start.matrix[1][0] = 1;
start.matrix[1][1] = 0;
Matrix answer(2,2);
while(cin >> n) {
answer = QuickPower(start, n);
cout << answer.matrix[0][1] << endl; //matrix[0][1]和matrix[1][0]一样
}
return 0;
}

例题8.4 二叉树

求根结点为m的树中有多少结点数目问题
//分治-二叉树结点m所在子树总共有几个结点 2024-03-08
#include <iostream>
#include <cstdio>
using namespace std;
int NodeNumber(int m, int n) {
if(m > n) {
return 0;
} else { //注意要加m结点本身,拆成子问题,m结点的左右子树+m结点本身
return 1 + NodeNumber(2 * m, n) + NodeNumber(2 * m + 1, n);
}
}
int main()
{
int m, n;
while(cin >> m >> n) {
cout << NodeNumber(m, n) << endl;
}
return 0;
}

当结点m大于n时,以m为根结点的树为空,该数的结点数目必定为0,这个便是这个问题可被轻松求解的底层问题,也是递归出口

posted @   paopaotangzu  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示