*遞歸函數的概念*
函數用自身給出定義的函數
分治法要素
(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;//沒有找到
}