遞迴分治 --- 演算法思想介紹
一.遞迴分治的基本概念
遞迴的概念:直接或間接的呼叫自身的演算法稱為遞迴演算法.用函式自身給出定義的函式成為遞迴函式.
分治法的思想:將一個難以直接解決的大問題分割成一些規模較小的相同問題,以便各個擊破,即分而治之.
如果原問題可分割成k個子問題, 1<k<=n, 且這些子問題都可解,並可利用這些子問題的解求出原問題的解,那麼這種分治法就是可行的.有分治法產生的子問題往往是原問題的較小模式,這為使用遞迴技術提供了方便.在這種情況下,反覆利用分治手段,可以使子問題與原問題型別一致而其規模不斷縮小,最終是子問題縮小到容易求出其解,由此自然引出遞迴演算法.
分治與遞迴像一對孿生兄弟,經常同時應用在演算法設計中,並由此產生許多高效演算法.
二.遞迴分治演算法的適用條件
我們下面利用幾個例子,來看一看遞迴分治演算法什麼情況下能夠使用,以及應該如何使用.
從我們耳熟能詳的階乘函式和斐波那契數列開始,介紹一下遞迴思想:
階乘函式:可遞迴地定義為:
- n! = 0, n = 1 (此為邊界條件)
- n! = n*(n-1)!, n > 0 (此為遞迴方程)
邊界條件與遞迴方程是遞迴函式的二個要素,遞迴函式只有具備了這兩個要素,才能在有限次計算後得出結果.
int Factorial(int n) { if(n == 0) return 1; else return n*Factorial(n-1); }
Fibonacci數列:無窮數列1,1,2,3,5,8,13,21,34,55,……,稱為Fibonacci數列。它可以遞迴地定義為:
- F(n) = 0, n = 0
- F(n) = 1, n = 1
- F(n) = F(n-1) + F(n-2), n > 1
int fibonacci(int n)
{
if (n <= 1) return 1;
return fibonacci(n-1)+fibonacci(n-2);
}
階乘函式和Fibonacci數列這兩種遞迴可轉換為非遞迴方式,但並不是所有遞迴都可以轉換.
認識了簡單的遞迴之後,我們舉一個分治演算法的例子 --- 二分查詢:
已知不重複且從小到大排列的m個整數的陣列A[1...m],要求找到一個下標i,使得A[i] = x, 找不到返回0;
基本思想:
將m個元素分成個數大致相同的兩半,取A[mid]與x作比較。
- x = A[mid], 演算法終止
- x < A[mid], 在陣列的左半部繼續搜尋
- x > A[mid], 在陣列的右半部繼續搜尋
int BinarySearch(int A[], int x, int l, int r)
{
while(l <= r)
{
int mid = (l+r) / 2;
if(A[mid] == x) return mid;
else if(x < A[mid])
r = mid - 1;
else
l = mid + 1;
}
return 0;
}
分治法的適用條件(其實就相當於是遞迴演算法的適用條件了,因為分治法實現起來大部分是使用了遞迴):
- 該問題的規模縮小到一定的程度就可以容易地解決;
- 該問題可以分解為若干個規模較小的相同問題,即該問題具有最優子結構性質
- 利用該問題分解出的子問題的解可以合併為該問題的解
- 該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子問題
最後一條這條特徵涉及到分治法的效率,如果各子問題是不獨立的,則分治法要做許多不必要的工作,重複地解公共的子問題,此時雖然也可用分治法,但一般用動態規劃較好.
注意:人們從大量實踐中發現,在用分治法設計演算法時,最好使子問題的規模大致相同。即將一個問題分成大小相等的k個子問題的處理方法是行之有效的。這種使子問題規模大致相等的做法是出自一種平衡(balancing)子問題的思想,它幾乎總是比子問題規模不等的做法要好.
三.遞迴分治演算法總結
遞迴分治演算法的模板虛擬碼:
Divide-and-Conquer(P)
if(|P|<=n0) Adhoc(P) //若問題規模小於閾值,直接計算
divide P into smaller subinstances P1, P2, ... , Pk //否則,將大問題劃分為若干個小問題
for(i = 1; i<= k; i++) //依次計算每個小問題
yi = Divide-and-Conquer(Pi);
return Merge(y1, y2, ... , yk); //合併每個小問題的解,得到最終答案
設問題P(n)分解成k個規模為n/m的子問題,閥值n0=1,求解P(1)的時間耗費為O(1).將P(n)分解及合併成P(n)的解的時間為f(n),則分治法解規模為n的問題的最壞時間複雜性函式T(n)滿足:
- T(n) = T(1) = O(1)
- = T(n) = kT(n/m) + f(n)
答案很複雜,總之遞迴演算法對時間空間複雜度要求較高.
後面我會針對具體的幾個演算法題來應用遞迴分治思想解決,歡迎繼續閱讀.
參考畢方明老師《演算法設計與分析》課件.
如果覺得本篇文章對你有所幫助,歡迎大家來到我的個人部落格網站---喬治的程式設計小屋逛一逛吧.