递归
1.递归算法及递归函数的概念及其二要素
1.1定义:
1.1 直接或间接地调用自身的算法称为递归算法。
1.2 用函数自身给出定义的函数称为递归函数。
1.3 由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便:
1.4 在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子 问题缩小到很容易直接求出其解。这自然导致递归过程的产生。
1.5 分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法
1.6 两要素:递归方程和边界条件
1.2递归使用场合:
①数据结构本身是递归定义的,其实现算法往往是递归算法,比如二叉树和图等
②求解过程需要递归,算法描述简捷其易于理解及分析,比如阶乘计算
1.3常见使用场景
1.3.1 阶乘函数
public static int recursion(int num){//利用递归计算阶乘
int sum=1;
if(num < 0)
throw new IllegalArgumentException("必须为正整数!");//抛出不合理参数异常
if(num==1){
return 1;//根据条件,跳出循环
}else{
sum=num * recursion(num-1);//运用递归计算
return sum;
}
}
1.3.2 Fibonacci数列
public class Solution {
public int Fibonacci(int n) {
if(n == 0) {
return 0;
}
if(n == 1) {
return 1;
}
return Fibonacci(n-1) + Fibonacci(n-2);
}
}
1.3.3全排列问题
从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起 叫做从n个不同元素中取出m个元素的一个排 列,当m=n时所有的 排列情况叫全排列。
public static void arrange(char[] arr,int left,int right){
if (left == right){
System.out.println(Arrays.toString(arr));
}else{
for (int i = left ; i <= right; i++) {
swap(arr, left, i);
arrange(arr, left + 1, right);
swap(arr, left, i);
}
}
}
public static void swap(char[] arr,int i,int j){
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
1.3.4整数划分问题
n=m1+m2+…+mi; (其中mi为正整数,并且1 <= mi <= n),则{m1,m2,…,mi}为n的一个划分。
如果{m1,m2,…,mi}中的最大值不超过m,即max(m1,m2,…,mi)<=m,则称它属于n的一个m划分。这里我们记n的m划分的个数为f(n,m);
举个例子,当n=5时我们可以获得以下这几种划分(注意,例子中m>=5)
5 = 5
= 4 + 1
= 3 + 2
= 3 + 1 + 1
= 2 + 2 + 1
= 2 + 1 + 1 + 1
= 1 + 1 + 1 + 1 + 1
根据n和m的关系,考虑以下几种情况:
- 当n=1时,不论m的值为多少(m>0),只有一种划分即{1};
- 当m=1时,不论n的值为多少,只有一种划分即n个1,{1,1,1,…,1};
- 当n=m时,根据划分中是否包含n,可以分为两种情况:
(1) 划分中包含n的情况,只有一个即{n};
(2) 划分中不包含n的情况,这时划分中最大的数字也一定比n小,即n的所有(n-1)划分。因此 f(n,n) =1 + f(n,n-1); - 当n<m时,由于划分中不可能出现负数,因此就相当于f(n,n);
- 但n>m时,根据划分中是否包含最大值m,可以分为两种情况:
(1) 划分中包含m的情况,即{m, {x1,x2,…xi}}, 其中{x1,x2,… xi} 的和为n-m,可能再次出现m,因此是(n-m)的m划分,因此这种划分个数为f(n-m, m);
(2) 划分中不包含m的情况,则划分中所有值都比m小,即n的(m-1)划分,个数为f(n,m-1);因此 f(n, m) = f(n-m, m)+f(n,m-1);
综合以上情况,我们可以看出,上面的结论具有递归定义特征,其中(1)和(2)属于回归条件,(3)和(4)属于特殊情况,将会转换为情况(5)。而情况(5)为通用情况,属于递推的方法,其本质主要是通过减小m以达到回归条件,从而解决问题。其递推表达式如下:
-
f(n, m)= 1 (n=1 or m=1)
-
f(n, m)=f(n, n) (n<m)
-
1+ f(n, m-1) (n=m)
-
f(n-m,m)+f(n,m-1) (n>m)
代码如下 实现语言 java
public static int division(int n,int m){ if (n == 1 || m == 1){ return 1; } if (n == m) { return 1 + division(n, m - 1); } if (n < m) { return division(n, n); } //最后就是 n > m情况 包含有m划分 和 没有m划分 return division(n - m , m) + division(n, m - 1); }
1.3.5汉诺塔问题
设a,b,c是3个塔座。开始时,在塔座a上有一叠共n个圆盘,这些圆盘自下而上,由大到小地叠在一起。各圆盘从小到大编号为1,2,…,n,现要求将塔座a上的这一叠圆盘移到塔座b上,并仍按同样顺序叠置。在移动圆盘时应遵守以下移动规则:
- 规则1:每次只能移动1个圆盘;
- 规则2:任何时刻都不允许将较大的圆盘压在较小的圆盘之上;
- 规则3:在满足移动规则1和2的前提下,可将圆盘移至a,b,c中任一塔座上
思路:
B为中间塔 C为目标塔只需将A盘前N - 1个盘挪到B盘 将第n个塔挪到C塔,再将B塔所有N - 1个盘借助A塔
移动到C塔即可
代码 使用语言java
/**
* @Author rwdou
* @Description 假设起始位置都在a盘塔 有num个盘都移动到c塔
* @Date 10:57 2020/5/20
* @Param [num, a, b, c]
* @return void
**/
public static void move(int num,char a,char b,char c){
if (num == 1){
System.out.println("第一个盘从" + a + "->" + c);
}else{
//将上面num - 1个盘移动到中间盘(B盘)
move(num - 1, a , c, b);
//将最下面的盘移动到目标塔(C盘)
System.out.println("第" + num + "个盘移动到" + c + "盘");
//将num - 1个盘移动到目标盘(c盘)
move(num - 1, b, a, c);
}
}