递归与分治的思想
直接或间接地调用自身算法就是递归算法。用函数自身给出定义的函数就是递归函数。将一个难以直接解决的大问题分割成一些规模较小的相同问题就是分治。分治与递归就像是一对孪生兄弟,经常同时出现在算法设计中,并由此产生许多高效算法。
下面我们就来用Java实现一些递归和分治的问题
(一)阶乘函数 :n!
要求n的阶乘,我们只需要求出(n-1)的阶乘,再乘上n即可,而要求(n-1)的阶乘,只需要求出(n-2)的阶乘再…这就是一个递归的过程
public class Recursion {
static int sum;
public static void main(String[] args) {
Recursion j=new Recursion();
j.factorial(5);
System.out.println("sum="+sum);
}
//阶乘
public int factorial(int n) {
if(n==1) {
return 1; //退出循环
}
if(n>1) {
sum1=n*factorial(n-1);//递归运算
}
return sum1;
}
}
(二)Fibonacci数列
斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指一个从第三项开始,后一项等于前两项之和的数列。
F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈N)
public class Recursion {
static int sum;
public static void main(String[] args) {
Recursion j=new Recursion();
j.Fibonacci(5);
System.out.println("sum="+sum);
}
//Fibonacci数列
public int Fibonacci(int n) {
if(n==0||n==1)
return 1;
if(n>=2){
sum=Fibonacci(n-1)+Fibonacci(n-2); //递归调用
}
return sum;
}
}
(三)Hanoi塔问题
把A中的所有圆盘移动到B上,规则是:每次只能移动一个圆盘,任何时刻都不允许大圆盘在小圆盘上。
思考:假设A上有n个圆盘,我们只需要把(n-1)个圆盘挪到C上,再把A中的最后一个圆盘挪到B,最后把C上的(n-1)个圆盘挪到B即可。
Hanoi(n-1,a,c,b);
move(a,b);
Hanoi(n-1,c,b,a);
public class Recursion {
static char a;
static char b;
static char c;
static int i;
public static void main(String[] args) {
Recursion j=new Recursion();
j.Hanoi(2, a, b, c);
System.out.println("i="+i);
}
//Hanoi塔问题
//一共要移动多少次
void Hanoi(int n,char a,char b,char c) {
if(n>0) {
Hanoi(n-1,a,c,b);
move(a,b);
Hanoi(n-1,c,b,a);
}
}
void move(char a,char b) {
char temp=0;
temp=a;
a=b;
b=temp;
i++;
}
}
(四)全排列问题
什么是全排列?
从n个不同元素中任取m(m<=n)个元素,按照一定的顺序排列起来,当n=m时所有排列的情况就是全排列。举例:{a,b,c}的全排列就是 {a,b,c},{a,c,b},{b,a,c},{b,c,a},{c,a,b},{c,b,a}
具体操作过程是怎样的呢?
(图片来源于百度)
public class Recursion {
static int m=0;
public static void main(String[] args) {
Recursion j=new Recursion();
char arr[]={'a','b','c','d'};
j.Permutation(arr, 0, arr.length);
System.out.println("共有"+m+"个全排列");
}
//交换函数
void Swap(char array[],int m,int n) {
char temp=array[m];
array[m]=array[n];
array[n]=temp;
}
//递归实现全排列
void Permutation(char array[],int start,int end) {
if(start==end) {
//打印输出
for(int i=0;i<array.length;i++) {
System.out.print(array[i]);
}
System.out.println();
m++;//用于记录全排列的个数
}
for(int m=start;m<array.length;m++) {
Swap(array,m,start);
Permutation(array,start+1,end);
Swap(array,m,start);
}
}
}
(五)整数划分问题
将正整数n表示为一系列正整数之和,n=n1+n2+n3+…+nk,正整数n的这种表示称为n的划分。例如:正整数6有以下11种划分:
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;
设正整数n的划分个数为P(n),则P(6)=11;在所有整数n的划分中,将最大加数n1不大于m的划分个数记作q(n,m),建立如下递归关系
分析:
q(n,m)=1,当n=m=1;
q(n,m)=q(n,n),当m>n时;n表示的是和,而m表示的是最大加数,当m>n
时求得其实就是q(n,n);
q(n,n)=1+q(n,n-1);表示正整数本身和(n-1)的划分。
q(n,m)=q(n,m-1)+q(n-m,m),表示正整数n的最大加数n1不大于m的划分由n1=m的划分和n1<m的划分组成。
设计程序如下:
public class Recursion {
public static void main(String[] args) {
Recursion j=new Recursion();
j.q(6,6,0);
//打印一共有多少种划分
System.out.println(j.q(6,6,0));
}
//创建一个数组用来储存划分的所有情况
public static int arr[]=new int[100];
public int q(int n,int m,int i) {
if(n<m) {
return q(n,n,i);
}
//把m存入数组
arr[i]=m;
if((n==0||m==0)) {
print(i);
return 0;
}
if(n==1||m==1) {
if(n==1) {
print(i);
}else q(n-1,1,i+1);
return 1;
}
if(n==m) {
print(i);
return q(n,n-1,i)+1;
}
return +q(n-m,m,i+1)+q(n,m-1,i);
}
//打印所有的情况
public void print(int i) {
System.out.print("{");
for(int j=0;j<=i;j++) {
if(j==i)System.out.print(arr[j]);//最后一个数不用输出“+”号
else System.out.print(arr[j]+"+");
}
System.out.println("}");
}
}