递归分析问题步骤:
1.找重复
将问题化为小规模问题 1.找到一种划分方法
2.找到一种等价关系
3.找到递推公式
2.找变化
变化的量作为参数
3.找临界
临界条件作为递归的出口,单独判断
目录
一.切蛋糕思维
1.数组序列求和
问题描述:对数组的元素进行序列求和。
我们可以按照递归分析问题的基本步骤来求解问题
1.找重复【将大问题规模划为小问题规模】:数组第n个元素加数组第n+1个元素到数组末尾所有元素的和。
2.找变化【变化的量就是参数】:在此问题当中变化的量是数组的第几个元素,即从哪个下标开始进行计算[假设从0开始计算,则问题描述可以描述为第0个元素加第1个元素到数组末尾所有元素的和]
3.找临界:【当开始的位置不断移动,直到移动到数组最后一个元素的时候对其进行单独处理】
public int sum(int[] arr,int begin) {
if(begin==arr.length-1)
return arr[begin];
return arr[begin]+sum(arr,begin+1);
}
2.字符串的反转
问题描述:对给定的字符串进行顺序反转
我们可以按照递归分析问题的基本步骤来求解问题
1.找重复【将大问题规模划为小问题规模】:字符串最后一个字符拼接前面所有反转后的字符串
2.找变化【变化的量就是参数】:在此问题当中变化的量是最后一个字符串的位置。
3.找临界:【当最后一个字符的位置不断向前移动,直到移动到字符串的第一个字符】
举例:反转abcd
化为小规模:d字符拼接abc反转后的结果
c字符拼接ab反转后的结果
b字符拼接a反转后的结果
public String reverse(String str,int end) {
if(end==0)
return ""+str.charAt(0);
return str.charAt(end)+reverse(str,end-1);
}
二.多分支递归问题
递归问题可以分为
- 直接量 与 小规模子问题【前面的两个练习】也就是单路径
- 多个小规模子问题 多路径
斐波那契数列则适用于 多个小规模子问题。
斐波那契数列定义:
描述:斐波那契数列(Fibonacci sequence),指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n ≥ 2,n ∈ N*)
具体展开:斐波那契数列可以衍生为一些实际问题,比如爬楼梯问题和兔子繁殖问题
public int fb(int n) {
if(n==1||n==2)
return 1;
return fb(n-1)+fb(n-2);
}
三.用递归来求最大公约数
知识补充:
一.
几个整数中公有的约数,叫做这几个数的公约数;其中最大的一个,叫做这几个数的最大公约数。例如:12、16的公约数有1、2、4,其中最大的一个是4,4是12与16的最大公约数,一般记为(12,16)=4。12、15、18的最大公约数是3,记为(12,15,18)=3。
几个自然数公有的倍数,叫做这几个数的公倍数,其中最小的一个自然数,叫做这几个数的最小公倍数。例如:4的倍数有4、8、12、16,……,6的倍数有6、12、18、24,……,4和6的公倍数有12、24,……,其中最小的是12,一般记为[4,6]=12。12、15、18的最小公倍数是180。记为[12,15,18]=180。若干个互质数的最小公倍数为它们的乘积的绝对值。
二.
辗转相除法
给定两个数,求这两个数的最大公约数 辗转相除法
欧几里得算法又称辗转相除法,是指用于计算两个非负整数a,b的最大公约数。应用领域有数学和计算机两个方面。计算公式gcd(a,b) = gcd(b,a mod b)。
例如:假如需要求 100 和18 两个正整数的最大公约数,用欧几里得算法,是这样进行的:
100 / 18 = 5 (余 10)
18 / 10= 1(余8)
10 / 8 = 1(余2)
8 / 2 = 4 (余0)
至此,最大公约数为2
以除数和余数反复做除法运算,当余数为 0 时,取当前算式除数为最大公约数,所以就得出了 100 和 18 的最大公约数2。
public int gcd(int a,int b){
if(b==0) return a;//返回a,即返回上一步的b
return gcd(b,a%b);
}
四.汉诺塔问题
汉诺塔:汉诺塔(Tower of Hanoi)源于印度传说中,大梵天创造世界时造了三根金钢石柱子,其中一根柱子自底向上叠着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
我们先来分析1层盘子、2层盘子和3层盘子分别的移动:
- 当a柱子上只有一个盘子时只要把那个盘子直接移到c就行了,
- 有两个盘子的话把1号盘先移到b柱,在把2号盘移到c柱,最后把b柱上的1号盘移到c柱就行了
- 有3个盘子则先把可以区分开,将第3盘作为一个整体,前二盘作为一个整体,先把前2盘移动到b柱子,在把3号盘移到c柱,最后把b柱上的1、2号盘移到c柱就行了。【方法同两个盘子的情况,这时我们就可以发现递归的步骤重复的地方】
我们可以按照递归的思想,将其等价为每次可以看着移动两个盘子,分为两个部分,第一部分:前n-1个盘子。第二部分:最后一个盘子,每次移动两个部分的操作是相同的即:有两个盘子的话把第一部分盘先移到b柱,在把第二部分盘移到c柱,最后把b柱上的第一部分盘移到c柱就行了
举例:64号盘可以分为 第一部分:前63号盘 第二部分:第64号盘
依次递归 第64号盘可以暂时不管,前63号盘又可以分为2部分……直分到只有一个盘子为止,递归结束。
参考文章:汉诺塔详解
代码如下:
public class Main {
int m=0;
//a起始柱子,b辅助柱子,c结果柱子
public void hannuo(int n,char a,char b,char c) {
if(n==1) {
move(n,a,c);
}
else {
hannuo(n-1,a,c,b);//将a柱子的编号1到n-1盘子移动到b柱子
move(n,a,c);//将a柱子编号n盘子移动到c柱子
hannuo(n-1,b,a,c);//将b柱子的编号1到n-1盘子移动到c柱子
}
}
public void move(int n,char a,char c) {
System.out.println("第"+(++m)+"次移动,移动编号"+n+"盘子从"+a+"到"+c);
}
public static void main(String[] args) {
Main m=new Main();
m.hannuo(3, 'a', 'b', 'c');
}
}
结果如下:
第1次移动,移动编号1从a到c
第2次移动,移动编号2从a到b
第3次移动,移动编号1从c到b
第4次移动,移动编号3从a到c
第5次移动,移动编号1从b到a
第6次移动,移动编号2从b到c
第7次移动,移动编号1从a到c
此处我们要体会到递归如何将大规模问题划分为小规模的问题,在这个问题当中,我们发现可以将盘子划分为2个部分,而这两个部分的移动步骤是重复的,我们就可以考虑采用递归。再单独处理临界状态,只有一个盘子的状态即可求解。