一.什么是分治策略?
我的总结是:大分小,小合大。
这句话将贯穿下面所有递归算法。
首先简单了解下递归:
1.用例子学习递归
1)阶乘函数
阶乘函数的定义:
当n=0时,n!=1
当n>0时,n!=n(n-1)!
从上面我们可以知道递归的边界条件为n=0,递归方程也就是n(n-1),由此,我们可以写出下面代码:
int jieCheng(int n){
if(n==0) return 1;
return jieCheng(n-1)*n;
}
实例展示
public class RecursionStudy {
/**
* 阶乘算法的递归实现
* @param n
* @return
*/
static int jieCheng(int n){
if(n==0) return 1;
return jieCheng(n-1)*n;
}
public static void main(String[] args) {
System.out.println(jieCheng(4));
}
}
2)斐波那契数
问题描述:给出一个数,求出它的斐波那契数
1,1,2,3,5,8,13是一个斐波那契数列,当n=5时,它的斐波那契数为8,依次类推。
其公式定义为:
n<=1时, F(n)=1
n>1时, F(n)=F(n-1)+F(n-2)
很明显我们可以用递归的方式进行问题解决:
public int Fib(int n){
if(n<=1)
return 1;
else
return Fib(n-1)+Fib(n-2);
}
实例展示
public class RecursionStudy {
/**
* 斐波那契数
* @param n
* @return
*/
public static int Fib(int n){
if(n<=1)
return 1;
else
return Fib(n-1)+Fib(n-2);
}
public static void main(String[] args) {
for (int i=0;i<10;i++){
System.out.print(Fib(i)+" ");
}
}
}
2.解决全排列问题
1)问题描述
设计一个递归算法,生成n个元素{r1,r2,r3,r4,…,rn}的全排列。
在思考这个问题之前,我们知道了递归是在一个函数中调用自身有限嵌套来实现了。
所以我们首先可以尝试一层一层地来实现,这种方式当嵌套数很少的时候还好,但是,数量多了,这样根本行不通!
所以我们要真正理解递归,必须理解这句话:递归的关键是将其抽象成一个递推公式,不要去想一层层的调用关系,不要试图用人脑取分解递归的每个步骤,它是一种思想。
解决思路:由此,我们开始重新思考学习如何使用递归来解决这个问题
首先,我们需要得出递推公式:
R={r1,r2,r3,r4,r5,..rn}
我们需要先找出这个问题的最小子问题,其实我们可以发现,这个最小子问题其实就是两个元素的排列。
Rmin={r1,r2}的全排列为{r1,r2},{r2,r1} | Rmin={r1} 的全排列为{r1}
有了这个基础我们可以得到问题的解的递推公式:
Perm(R)为R的全排列结果集
Ri=R-{ri}
当n>1时,有:
Perm(R)=(r1)Perm(R1)+(r2)Perm(R2)+...+(rn)Perm(Rn);然后以此类推
现在我们可以用一个例子来分析验证:R={1,2,3}
Perm(R)=(1)Perm({2,3})+(2)Perm({1,3})+(3)Perm({1,2})
={1,2,3}+{1,3,2}+{2,1,3}+{2,3,1}+{3,1,2}+{3,2,1}
可以知道有六种情况。发现可行,下面我们用代码实现:
/**
* 全排列问题
* @param a
* @param cursor
* @param k
*/
public static void allPermutation(int[] a,int cursor,int k){
//递归终止条件
if(cursor==k){
System.out.println(Arrays.toString(a));
}
for(int i=cursor;i<=k;i++){
if(!equal(a,cursor,i)) continue;
swap(a,cursor,i);
allPermutation(a,cursor+1,k);
swap(a,cursor,i);
}
}
public static void swap(int[] a,int cursor,int i){
int temp = a[cursor];
a[cursor]=a[i];
a[i]=temp;
}
public static boolean equal(int[] a,int cursor,int i){
for(int j=cursor;j<i;j++){
if(a[j]==a[i]) return false;
}
return true;
}
@Test
public void allPermutationTest(){
int[] a={1,2,3};
allPermutation(a,0,a.length-1);
}
3.解决整数划分问题
1)问题描述
将正整数n表示成一系列正整数之和:n=n1+n2+n3+…+nk;
其中n1>=n2>=n3>=n4…>=nk>=1 k>=1,正整数n的这种表示称为正整数n的划分。
求正整数n的不同划分个数
例如:正整数6有如下划分个数
6;
5+1;
4+2 , 4+1+1;
3+3 , 3+2+1 , 3+1+1+1
2+2+2 , 2+2+1+1 , 2+1+1+1+1
1+1+1+1+1+1+1
从这个问题我们也能很快找到最小子问题 就是整数n=1时,它的划分为 1 ,n=2时,它的划分为 1+1
q(6)=0+q(6) + 1+q(5) + 2+q(4) + 3+q(3) 以此类推
代码如下:
public static int intDivision(int n,int m){
if(n<1||m<1) return 0;
if(n==1||m==1) return 1;
if(n<m) return intDivision(n,n);
if(n==m) return intDivision(n,m-1)+1;
return intDivision(n,m-1) + intDivision(n-m,m);
}
@Test
public void intDivisionTest(){
System.out.println(intDivision(6,6));
}
注意:我们不建议使用递归,在算法中,使用递归的缺点大于它的优点,这一节只是对算法有一些基本的了解,了解即可。