算法筆記(三):遞歸複雜度的計算、主定理、漸進符號

    有些同學可能會很困惑:時間複雜度的表示怎麼一會兒是 大O,  一會兒是\Omega(讀作Omega),一會兒又是\Theta(讀作Theta)?

這三個符號略有區別,要用數學語言才能描述,略顯枯燥,我們到後面再聊大O、\Omega\Theta表示時間複雜度的區別,大家先記住,大O、\Omega(Omega)、\Theta(Theta)都是表示時間複雜度的3種漸進符號;總的來說 ,大O是小於等於, \Omega是大於等於;\Theta是等於。


目錄

大O、、表示時間複雜度的區別

漸近分析asymptotic analysis:

上界:big O notation

 下界: big Omega notation

代換法

遞歸樹求時間複雜度

歸併排序遞歸樹

斐波那契遞歸樹

快速排序遞歸樹

全排列的時間複雜度

主定理



Insertion Sort;Sort A[1,..,n]
# 插入排序的僞代碼;eg. A= [8,2,4,9,3,6]
# 由於第2個元素是2,把2移到8前面:2,8,4,9,3,6; 再看第3個元素4,得到2,4,8,9,3,6,一直循環下去;
for j <- 2 to n:
    do key <- A[j] # 構造一個數組A,其中元素從2到j是已經排序好的有序部分;
        i <- j-1               # 內循環從 j-1 減到0
        while i > 0 and A[i] > key:
            do A[i+1]  <- A[i]
                i <-  i-1
        A[i+1]  <- key

插入排序的時間複雜度分析;
最壞:  數組AT\left ( n \right )= \sum_{j=2}^{n}\theta \left ( j \right )=\Theta \left ( n^{2} \right )是逆序的,     (等差數列,算術級數)

對於n很小時,插入排序較快,當n很大時不快。

 

大O、\Omega\Theta表示時間複雜度的區別

大O、\Omega(Omega)、\Theta(Theta)都是表示時間複雜度的3種漸進符號;總的來說 ,大O是小於等於, \Omega是大於等於;\Theta是等於。

算法的運行時間取決於輸入數據的規模n;把時間複雜度看作n的函數;定義T(n)爲輸入規模爲n時的最長運行時間。

漸近分析asymptotic analysis:

不考慮代碼運行時具體的硬件環境;關注運行時間隨着數據規模的增長幅度。用忽略低階項和常數係數的漸近符號\Theta表示。

上界:big O notation

f\left ( n \right )=O\left ( g\left ( n \right ) \right )定義爲:存在適當的常數 c> 0,\: n_{0}> 0使得 0\leqslant f\left ( n \right ) \leqslant c\ast g\left ( n \right ) 對所有n\geqslant n_{0}成立, 假設f\left ( n \right )非負;也就是 O\left ( g\left ( n \right ) \right )可看作一個函數集合 \left \{ f\left ( n \right ) \right \} ,存在適當的常數 c> 0,\: n_{0}> 0使得f\left ( n \right )g\left ( n \right )的常數倍爲上界

比如2n^{2}=O\left ( n^{3} \right ) 就表示去掉首項係數2和低階項之後剩下的n^{2}小於等於n^{3}。eg n^{2}+O\left ( n \right ) =O\left ( n^{2} \right )

注:這裏大O前面的等號不是對稱的,我們不能由f\left ( n \right )=O\left ( g\left ( n \right ) \right )推出g\left ( n \right )=O\left ( f\left ( n \right ) \right )

eg. f\left ( n \right ) = n^{3}+O\left ( n^{2} \right )表示O\left ( n^{2} \right )的函數集中存在某個函數 h\left ( n\right ),使得f\left ( n \right ) = n^{3}+h\left ( n \right );h\left ( n\right )可看作誤差項。

 下界: big Omega notation

\Omega \left ( g\left ( n \right ) \right )=\left \{ f\left ( n \right ) \right \}表示存在常數 c> 0,\: n_{0}> 0使得 0\leqslant c\ast g\left ( n \right ) \leqslant f\left ( n \right ) 對所有n\geqslant n_{0}成立,也就是當n足夠大時,有f\left ( n \right )大於等於g\left ( n \right )的某個常數倍;

比如\sqrt{n}=\Omega \left ( lgn \right ); 也就是\sqrt{n}漸近地大於 lgn。

\Theta【capital theta】是大於等於且小於等於; o 【little o】是嚴格小於; \omegalittle omega】是嚴格大於。


求解遞歸式的方法有3種:代換法【substitution method】;遞歸樹【recursion tree】

代換法

   先猜測解的形式,再用數學歸納法驗證,再求解常數項。也就是求出具體的滿足上述定義的c> 0,\: n_{0}> 0 來。

 eg已知遞歸式  T\left ( n \right )=4T\left ( \frac{n}{2} \right )+n;\: T\left ( 1 \right )=\Theta \left ( 1 \right )

我們先猜測T\left ( n \right )=O\left ( n^{3} \right );也就是假設對所有k< nT\left ( k \right )\leqslant c \cdot k^{3} \:; 通過數學歸納【Induction】有T\left ( n \right )=4T\left ( \frac{n}{2} \right )+n\leqslant 4c \cdot \left ( \frac{n}{2} \right )^{3}+n=\frac{c}{2}n^{3}+n\leqslant c\cdot n^{3};\: \: if \: \: \frac{c}{2}n^{3}-n\geqslant 0 ;要保證餘項residual =c\cdot n^{3}- \left ( \frac{c}{2}n^{3}+n \right ) =\frac{c}{2}n^{3}-n\geqslant 0,所以當c \geqslant 1時,不等式\frac{c}{2}n^{3}-n\geqslant 0恆成立。

證明T\left ( n \right )=O\left ( n^{3} \right ):假設T\left ( k \right )\leqslant c \cdot k^{3} \:對所有k< n都成立;則T\left ( n \right )=4T\left ( \frac{n}{2} \right )+n\leqslant 4c \cdot \left ( \frac{n}{2} \right )^{2}+n=\frac{c}{2}n^{2}+n\geqslant c\cdot n^{2};n\leqslant 0時纔有T\left ( n \right )\leqslant c\cdot n^{2};所以不成立。

改進Induction hypothesis :     假設對所有k< nT\left ( k \right ) \leqslant c_{1} \cdot k^{2} -c_{2} \cdot k; 則T\left ( n \right )=4T\left ( \frac{n}{2} \right )+n =c_{1} \cdot n^{2} + \left ( 1-2c_{2} \right )\cdot n =c_{1} \cdot n^{2} -c_{2} \cdot n-\left ( -1+ c_{2} \right )\cdot n;要證明T\left (n \right ) \leqslant c_{1} \cdot n^{2} -c_{2} \cdot n;則餘項\left ( -1+ c_{2} \right )\cdot n> 0;所以c_{2} \geqslant 1。也就是當c_{2} \geqslant 1時成立,T\left ( 1 \right ) \leqslant c_{1} -c_{2} 是常數,所以c_{1}要大於c_{2};也就是c_{1}要足夠大。 


遞歸樹求時間複雜度

形如T\left ( n \right )=a\ast T\left ( n/b \right )+cn 的遞歸樹可以用後面講到的主方法、主定理求解。遞歸思想:是將大問題分解爲小問題來求解,然後再將小問題分解爲更小的問題。這樣一層一層地分解,直到問題規模被分解得足夠小,不用繼續遞歸分解爲止。這種方法不太嚴謹;有待證明;通常用遞歸樹找出答案,再用代換法來驗證。


歸併排序遞歸樹

#歸併排序僞代碼
merge_sort A[1,...,n]
1. if n =1 ,done,
2. 對A[1,...,ceil(n/2)] 和A[ceil(n/2)+1 ,...,n]遞歸調用歸併排序;其中ceil(n/2)是對n/2取上限
3. 把第2 步得到的兩個排序完的表合併。

關鍵在於第3 步的merge,假設有2個排序好的數組 [2,7,13,20] 和[1,9,11,12];由於兩個數組都是有序的,所以只需要比較兩個數組的第一個元素,找出最小的那個並寫到最終的數組中,再把指針向後移一,再比較剩下的兩個數組中的元素誰最小;所以先把 1寫入到數組中再去掉1 (或者指針後移),由於每次只比較兩個元素(和數組元素的數目無關),有【1,2,7,9,11,12,20】所以第3 步的時間爲2n for n total elements.

對於歸併排序的時間複雜度有:

T(n)= \begin{cases}\Theta \left ( 1 \right ) & \text{ if } n=1 \\ 2T\left ( n/2 \right )+\Theta \left ( n \right )& \text{ if } n >1 \end{cases}

其中第三步的時間複雜度爲\Theta \left ( n \right );第二部由於把數組從中間位置一分爲二再歸併,所以爲2T\left ( n/2 \right );所以n大於1 的情形,T(n)就等於第2、3步的時間相加。


先來考慮歸併排序的這種情形,由於隱式函數\Theta \left ( n \right )和cn都是一階的,我們用cn代替\Theta \left ( n \right )T\left ( n \right )=2T\left ( n/2 \right )+cn ;其中c爲大於0 的常數;每次分解都是一分爲二,我們把時間上消耗記敘常量O(1). 把它寫成遞歸樹,就相當於是對歸併排序算法第2、3步的逐步二分 log_{2} n 次,直到最後都爲葉子節點【時間複雜度爲O(1)】。歸併排序遞歸樹的構造方法如下圖;

現在只需要知道這棵樹的高度h,用高度h 乘以每一層的時間消耗n,就可以得到總的時間複雜度O(n*h)。由於歸併排序是一棵滿二叉樹,那h = log2n ,再由於之前講過的的【對數階的複雜度可以通過換底公式相互轉換,所以用lg表示對數階】 所以粗略估計的歸併排序的時間複雜度就是O(nlogn)。

由上圖可以看出,每一層的運行時間都是cn,葉子層的時間爲\Theta \left ( n \right ).所以總的運行時間爲cnlgn+\Theta \left ( n \right )=\Theta \left ( nlgn \right ) .在漸近情況下比\Theta \left ( n^{2} \right )要快。所以當輸入規模n足夠大時,歸併排序要優於插入排序。


斐波那契遞歸樹

再以一棵斐波那契數列【1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...;它的第n項可寫作F(n)=F(n-1)+F(n-2)】的遞歸樹爲例,節點的數字表示數據的規模,一個節點的求解可以分解爲左右子節點兩個問題的求解。

   f(n) 分解爲f(n - 1) 和 f(n-2),每次數據規模都是-1或者-2,葉子節點的數據規模是1或者2。所以,從根節點到葉子節點,每條路徑長度是不一樣的。如果每次都是-1,那最長的路徑大約是n;如果每次都是 -2,那最短路徑大約就是 n/2。每次分解之後的合併操作只需要一次加法運算,我們把這次加法運算的時間消耗記作1; 從上往下,第一層總時間消耗是 1,第二層總時間消耗是 2,第三層的總時間消耗就是4,依次類推,第k+1 層的時間消耗就是2^k。如果路徑長度爲n,那這個總全就是 2^n -1。 如果路徑長度都是 n/2,那整個算法的總時間消耗就是2^(n/2) -1。所以,這個算法的時間複雜度就介於O(2n)和O(2n)/4之間。


快速排序遞歸樹

在最好情況下,快速排序每次分區都能一分爲二,這個時候用遞推公式 T(n) = 2T(n/2) + n,很容易就能推導出時間複雜度是O(nlogn)。假設平均情況下,每次分區之後,兩個分區的大小比例爲 1:k。當 k =9 時,如果用遞推公式的方法來求解時間複雜度的話,遞推公式就寫成 T(n) = T(n/10) + T(9n/10) + n。把遞歸分解的過程畫成遞歸樹得到下圖。

快速排序的每次分區都要遍歷待分區區間的所有數據,所以每一層分區操作遍歷的數據個數之和就是n。只要求出遞歸的高度h,就可以得出快排過程的時間複雜度O(hn)。快速排序結束的條件是待排序的小區間大小爲1。從根節點n 到葉子節點1,遞歸樹中最短的一個路徑是每次都乘以 1/10,最長的路徑是每次都乘以9/10。根據複雜度大O表示法,對數複雜度的底數不管是多少,我們統一寫成logn,所有當大小比例是1:9時,快速排序的時間複雜度仍然是O(nlogn)。


全排列的時間複雜度

全排列問題: 如何把n 個數據( 1,2,3...n)的所有排列都找出來.。如何藉助遞歸樹,分析出這個代碼的時間複雜度。

/*
*假設數組中存儲的是 1,2,3...n。
*f(1,2,3...n) = {最後一位是1,f(n - 1)} + {最後一位是2,f(n - 1)} + ...+ {最後一位是n,f(n - 1)}
 */
public void pringPermutations(int[] data, int n, int k) {
	// n 表示數組大小,k表示要處理的子數組的數據個數
	if (k == 1) {
		for (int i = 0; i < n; i++) {
			System.out.print(data[i] + " ");

		}
		System.out.println();
	}
	for(int i = 0; i < k; ++i) {
		int tem = data[i];
		data[i] = data[k-1];
		data[k-1] = tem;
		
		pringPermutations(data, n, k-1);
		
		tem = data[i];
		data[i] = data[k-1];
		data[k-1] = tem;
	}
}

第一層分解有n次交換操作,第二層有n 個節點,每個節點分解需要 n-1 次交換,所以第二層的交換次數是n*(n-1),同理,第三層交換次數就是n*(n-1)(n-2)。各層交換次數總合就是 n + n(n-1) + n*(n-1)(n-2) +… +n! 。也就是說全排列的遞歸算法的時間複雜度大於O(n!),小於O(nn!)。


主定理

前面我們只是列舉了一部分遞歸樹求解時間複雜度的方法,但是更廣泛的訣竅是用Master method,它雖然限制很多但是應用超級方便,

它只適用於形如T\left ( n \right )=a\ast T\left ( n/b \right )+f\left ( n \right )的遞歸式中:有a個子問題;每個子問題的數據規模是n/b;還要滿足a\geqslant 1,\: b> 1,  f(n)要是漸近趨正(asympotically positive)的,這類遞歸樹的畫法規則爲:

  (1)每個節點的分支數爲a;

  (2) 每層的節點爲T(n) = aT(n / b) + f(n)中的f(n)在當前的n/b下的值。

  (3)每層的右側標出當前層中所有節點的和。

下面的主定理給出3種case:

case2在算法導論課中又寫作 :存在某個 \large k\geqslant 0  ; 使得  \large f\left ( n \right )=\Theta \left ( n^{log_{b} a} \left ( lgn \right )^{k} \right ) ;則 有下式成立;

 \large T\left ( n \right )=\Theta \left ( n^{log_{b} a} \left ( lgn \right )^{k+1} \right ) =f\left ( n \right )\cdot lgn.

對於case3中\large a \cdot f \left ( \frac{n}{b} \right ) 表示下一層的所有值之和。

例題 : 下面的4個遞推式的f(n)不同導致的時間複雜度也不同。

【1】    \large T\left ( n \right )=4T\left ( \frac{n}{2} \right )+n\: ; \:

由於\large a=4,b=2,f\left ( n \right )=n; 屬於case1, \large n^{\textup{log}_{b}a} =n^{2}, 所以有\large T\left ( n \right )=\Theta \left ( n^{2} \right )

【2】   \large T\left ( n \right )=4T\left ( \frac{n}{2} \right )+n^{2}\: ; \:

由於\large a=4,b=2,f\left ( n \right )=n^{2}= \Theta \left ( n^{log_{b} a} \left ( lgn \right )^{0 \right ) ; 屬於case2, \large n^{\textup{log}_{b}a} =n^{2}\: ;k= 0, 所以有\large T\left ( n \right )=\Theta \left ( n^{2} lgn \right )

【3】   \large T\left ( n \right )=4T\left ( \frac{n}{2} \right )+n^{3}\: ; \:

由於\large a=4,b=2,f\left ( n \right )=n^{3}= \Omega \left ( n^{log_{b} a+\epsilon } \left \right ) ; 屬於case3, \large n^{\textup{log}_{b}a} =n^{2}\: ;\epsilon =1, 所以有\large T\left ( n \right )=\Theta \left ( n^{3} \right )

【4】   \large T\left ( n \right )=4T\left ( \frac{n}{2} \right )+\frac{n^{2}}{lgn} \: ; \:    【不適用master method 】則 \large T\left ( n \right )=\Theta \left ( n^{2} \textup{ lg} lgn \right ) .

 

 


參考資料:

【1】主定理與遞歸樹計算算法時間複雜度

【2】遞歸樹: 如何藉助樹來求解遞歸算法的時間複雜度

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章