分治算法之递归概述
文章目录
递归定义
在调用一个函数的过程中又出现了直接或间接调用该函数本身
直接调用
p函数定义中调用p函数
间接调用
p函数定义中调用q函数
递归的要素
- 递归表达式(递归方程)
- 递归结束条件(边界条件)
示例:
sum ( n ) = { 1 n = 1 sum ( n − 1 ) + n n > 1 \operatorname{sum}(\mathrm{n})=\left\{\begin{array}{cl} 1 & \mathrm{n}=1 \\ \operatorname{sum}(\mathrm{n}-1)+\mathrm{n} & \mathrm{n}>1 \end{array}\right. sum(n)={1sum(n−1)+nn=1n>1
n=1为边界条件,n>1时为递归方程
能用递归解决的问题需要满足的条件
- 需要解决的问题可以转化为一个或者多个子问题来求解,而这些子问题的求解方法与原问题完全相同,只是数量规模不同
- 递归调用的次数必须是有限的
- 必须有结束递归的条件来终止递归
递归的应用场景
- 定义是递归的(阶乘,斐波那契数列等)
- 数据结构是递归的(单链表,二叉树等)
- 问题求解方式是递归的(汉诺塔,回溯法等)
递归的优缺点
优点:
结构清晰,可读性强,容易用数学归纳法来证明算法的正确性
缺点:
运行效率较低,费时费空间
解决方法:可以将某些递归转化为非递归算法(PS:估摸着就是把尾递归转化为循环,个人想法,未必正确)
实例—汉诺塔问题
问题描述:
A,B,C三根柱子,n个圆盘,自下而上,由大到小
将这n个圆盘从A柱移动到C柱,并且在C柱也需要按照从下往上由大到小的顺序叠放
问题规则:
- 每次只能移动一个圆盘
- 任何时刻都不允许吧大的圆盘放在小的上面
- 在满足上述两个规则下,可以将圆盘移到A,B,C中任意一根柱子上
设计思路:
(1)将n-1个圆盘从A->B
(2)将一个圆盘从A->C
(3)将n-1个圆盘从B->C
//伪代码
input n
begin
if n>0
Hanoi(n-1,A,C,B)
Move(A,C)
Hanoi(n-1,B,A,C)
end
时间复杂度分析
T(1)=O(1)
T(n)=2T(n-1)+O(1)=( 2 n 2^{n} 2n-1)*O(1)=O( 2 n 2^{n} 2n)
//C/C++实现
#include<stdio.h>
int step;
void move(int n,char a,char b){
printf("%d: move %d from %c to %c.\n",++step,n,a,b);
}
void hanoi(int n,char a,char b){
if(n>0){
hanoi(n-1,a,c,b);
move(n,a,c);
hanoi(n-1,b,a,c);
}
}
int main(){
int n;
scanf("%d", &n);
hanoi(n,'A','B','C');
}
递归算法设计问题合集
问题1:递归算法执行中的递归状态表示
在函数的参数引用中,我们有两种引用方式:(1)int f(int n) (2)int f(int &n) 对于此上两种方式进行讲解
(1)int f(int n) 此种方式是将n放入到系统栈中,在函数结束时自动返回,咋递归函数中每次的这种变量实质上是不同的变量,彼此之间并无关系
(2)int f(int &n) 此种方式是将n放入一个指定的内存空间中,在递归函数中每次对此的引用实质上是对同一个变量的引用·
问题2:递归算法执行中临时参数的初始化
递归调用分为两部分:结束条件和递归方程,于是临时变量的初始化就会有两种情况(1)对于满足结束条件是的临时变量(2)对于不满足结束条件,仍然处于递归方程中的临时变量,这两种情况必须搞清楚(PS:这个我觉得只要涉及临时变量有使用递归的人一般都要设置的吧。。。)
递归函数转化为非递归函数
将递归函数转化为非递归函数我们可以通过栈
我们在设计栈时除了要保存递归函数的参数以外,我们还需要设计一个标志成员,用来展示这个小问题是否被得到解决
注:用结构体来完成需要被保存的属性