*递归函数的概念*
函数用自身给出定义的函数
分治法要素
(1)将一个规模较大的问题划分成规模小的相同子问题。
(2)求子问题的解(子问题独立),从而解决原问题的解。
以下是递归函数的实例
1。阶乘函数
阶乘函数可递归地定义为:
n!={1,n=0;n(n-1)! ,n>0}
递归函数的自变量n的定义域为非负整数,递归式的第一式给出了这个函数的初值,是非递归的定义。每个递归函数都必须是由非递归定义的初始值,否则无法计算。递归式的第二式使用较小的变量的函数值来表达较大的自变量的函数值的方式来定义n的阶乘。
public static int factorial(int n)
{
if(n==0) return 1;
return n*factorial(n-1);
}
2.Fibonacci数列
无穷数列1,1,2,3,5,8,13,21,34,.......................,称为Fibonacci数列。他可以递归的定义为:
F(n)={1,n=0,1; F(n-1)+F(n-2), n>1}
这是一个递归关系式,他说明当n>1时,这个数列的第n项的值是它前面两项之和,他用较小的自变量的函数值定义较大自变量的函数值,所以只需要连个初始值F(0)和F(1)。
public static int Fibonacci(int n)
{
if(n>1)
return 1;
return Fibonacci(n-1)+Fibonacci(n-2);
}
3.排列问题
设R={r1,r2,............rn}要进行排列的n个元素,Ri=R-{ri},集合X中元素的全排列。记为perm(X)。(ri)perm(X)表示在全排列perm(X)的每一个排列前面加上前缀ri得到的排列。R的全排列可归纳为:
当n=1,perm(R)=(r),其中r是集合R中唯一元素。
当n>1,perm(R)由(r1)perm(R1), (r2)perm(R2),...............(rn)perm(Rn)构成。
算法如下:
public static void perm (Object [] list,int k,int m)
//产生list[k:m]的所有排列
{
if(k==m)
{
//只剩一个元素
for(int i=0;i<=m;i++)
System.out.print(list [i]);
System.out.print();
}
else
//还有多个元素,递归产生排列
for(int i=k;i<=m;i++)
{
MyMath.swap(list,k,i);
perm(list,k+1,m);
MyMath.swap(list,k,i);
}
}
4.Hanoi问题
设a,b,c是三个塔座。开始时,在塔座a上有一叠共n个圆盘,这些圆盘自上而下,从大到小叠在一起,各圆盘从小到大编号为1,2,................n。
(1)每次只能移动1个圆盘
(2)任何时刻都不允许将较大的圆盘压在较小的圆盘之上。
(3)在满足以上规则的前提下,可将圆盘移至a,b,c中任何一塔座上。
解决方法:
当n=1时,将编号为1的圆盘从塔座上a直接移至b上。
当n>1时,需要借助塔座c作为辅助塔座,将n-1个较小的圆盘依照规则从塔座a移至塔座c,然后将剩下的最大圆盘从a移至b,最后设法将n-1个较小的圆盘依照规则从塔座c移至b。由此看出来,n个圆盘的移动问题可分为两次n-1个圆盘的移动问题,
算法:
public static void hanoi(int n,int a,int b,int c)
{
if(n>0)
{
hanoi(n-1,a,c,b)
move(a,b);
hanoi(n-1,c,b,a);
}
}
5.合并排序
合并排序算法是用分治策略实现对n个元素进行排序的算法,基本思想是分成大小大致相同的2个集合,分别对2个子集合进行排序,最终将排好序的子集合合并成为要求的排好序集合。
算法:
public static void mergeSort(Compareable a[],int left,int right)
{
if(left<right)//至少两个元素
int i=(left+right)/2;//取中点
mergeSort(a,left,i);//调用函数
mergeSort(a,i+1,right);
mergeSort(a,b,left,i,right);//合并到b数组
copy(a,b,left,right);//复制回数组a
6.快速排序
基本思想是:对于输入的子数组a[p:r],按照以下三个步骤进行排序:
(1)分解,以a[p]为基准元素将a[p:r]划分成3段a[p:q-1],a[q]和a[q+1:r],使得a[q-1:r]中任何元素小于等于a[q],a[q+1:r]中任何元素大于等于a[q]。下标q在划分过程中确定。
(2)递归求解,通过递归调用快速排序算法,分别对a[p:q-1]和a[q+1:r]进行排序。
(3)合并,由于对a[p:q-1]和a[q+1:r]的排序是就地进行排序的,所以在a[p:q-1]和a[q+1:r]都已排好的序后不需要执行任何计算,a[p:r]就已排好序。
算法:
private static void qSort(int p,int r)
{
if(p<r)
int q=partition(p,r);//位置
qSort (p,q-1);//对左半端进行排序
qSort (q+1,r);//对右半端进行排序
}
/** 对含有n个元素的数组a[o,n-1]进行快排只要调用qSort(a,o,n-1)。
上面的算法是的partition,以确定的基准元素a[p]对子数组a[p:r]进行划分,它是快速排序的关键。**/
pravite static int partition(p,r)//调用函数partition
{
int i=p;
j=r+1;
Comparable X=a[p];
//将小于X的元素交换到左区域
//将大于X的元素交换到右区域
while (true)
{
while(a[i++],CompareTo(X)<0&&i<r)
while(a[--j],CompareTo(X)>0)
if(i>=j)
break;
MyMath.swap(a,i,j);
}
a[p]=a[j];
a[i]=x;
return j;
}
/**
partition对a[p:r]进行划分时,以x=a[p]作为划分的基准,分别从左右两端开始,扩展两个区域a[p:i]和a[j:r]使得a[p:i]中元素小于等于x,而a[j:r]中大于或等于x,初始值为i=p,j=r+1。
7.线性时间选择
划分基准:
(1)将n个输入元素划分为[n/5]个组,每组5个元素,只可能有一个组不是5个元素。用任意一种排序算法,将每组中的元素排好序,并取出每组的中位数,共[n/5]个。
(2)递归调用算法select来找出这[n/5]个元素的中位数,如果[n/5]是偶数,就找出他的两个中位数中较大的一个,以这个元素作为划分基准。
pravite static Compareabe select(int p,int r,int k)
//p是第一个元素,r是最后一个元素,k表示第k小
{
if(r-p<5)//至少5个元素
//用某个简单排序算法对数组a[p:r]排序
bubbleSort(p,r);//冒泡排序
return a[p+k-1];
}
//将a[p+5*i]至a[p+5*i+4]的第3小元素
//与a[p+i]交换位置
//找中位数的中位数,r-p-4即就是上面说的n=5。
for(int i=0;i<=(r-p-4)/5;i++)//控制分组
{
//s是首元素,t是最后一个元素
int s=p+5,
t=s+4;
for(int j=0;j<3;j++)
bubbleSort(s,t-j);
//p+2代表首元素,s+2代表中位数。
MyMath.swap(a,p+i,s+2);
}
Compareable x=select(p,p+(r-p-4)/5,(r-p+6)/10);
//找中位数
int i=partition(p,r,x);
j=i-p+1;
if(k<=j)
return select(p,i,k);
else return select(i+1,r,k-j);
}
8.二分搜索技术
二分搜索算法是运用分治策略典型的例子
基本思想:n个元素分成个数大致相同的两半,取a[n/2]与x进行比较,如果x=a[n/2]则找到x,算法终止。如果x<a[n/2],则只要在数组a的左半部分继续搜索x,如果 x>a[n/2],则只要在数组a的右半部分继续搜索x。
算法如下:
public static int binarySearch(int [] a,int x,int n)
{
//在a[0]<a[1]<..............<a[n-1]中搜索x
//找到x时返回其在数组中的位置,否则返回-1
int left=0;
int right=n-1;
while(left<right)
{
int middle=(left+right)/2;
if(x==a[middle])
return middle;
if(x>a[middle])
left=middle+1;
else right=middle-1;
}
return -1;//没有找到
}