有些同學可能會很困惑:時間複雜度的表示怎麼一會兒是 大O, 一會兒是(讀作Omega),一會兒又是(讀作Theta)?
這三個符號略有區別,要用數學語言才能描述,略顯枯燥,我們到後面再聊大O、、表示時間複雜度的區別,大家先記住,大O、(Omega)、(Theta)都是表示時間複雜度的3種漸進符號;總的來說 ,大O是小於等於, 是大於等於;是等於。
目錄
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
插入排序的時間複雜度分析;
最壞: 數組A是逆序的, (等差數列,算術級數)對於n很小時,插入排序較快,當n很大時不快。
大O、、表示時間複雜度的區別
大O、(Omega)、(Theta)都是表示時間複雜度的3種漸進符號;總的來說 ,大O是小於等於, 是大於等於;是等於。
算法的運行時間取決於輸入數據的規模n;把時間複雜度看作n的函數;定義T(n)爲輸入規模爲n時的最長運行時間。
漸近分析asymptotic analysis:
不考慮代碼運行時具體的硬件環境;關注運行時間隨着數據規模的增長幅度。用忽略低階項和常數係數的漸近符號表示。
上界:big O notation
定義爲:存在適當的常數 使得 對所有成立, 假設非負;也就是 可看作一個函數集合 ,存在適當的常數 使得以的常數倍爲上界;
比如 就表示去掉首項係數2和低階項之後剩下的小於等於。eg 。
注:這裏大O前面的等號不是對稱的,我們不能由推出。
eg. 表示的函數集中存在某個函數 ,使得;可看作誤差項。
下界: big Omega notation
表示存在常數 使得 對所有成立,也就是當n足夠大時,有大於等於的某個常數倍;
比如; 也就是漸近地大於 lgn。
【capital theta】是大於等於且小於等於; o 【little o】是嚴格小於; 【little omega】是嚴格大於。
求解遞歸式的方法有3種:代換法【substitution method】;遞歸樹【recursion tree】
代換法
先猜測解的形式,再用數學歸納法驗證,再求解常數項。也就是求出具體的滿足上述定義的 來。
eg已知遞歸式 ,
我們先猜測;也就是假設對所有有; 通過數學歸納【Induction】有 ;要保證餘項,所以當時,不等式恆成立。
證明:假設對所有都成立;則 當時纔有;所以不成立。
改進Induction hypothesis : 假設對所有有 ; 則;要證明;則餘項;所以。也就是當時成立, 是常數,所以要大於;也就是要足夠大。
遞歸樹求時間複雜度
形如 的遞歸樹可以用後面講到的主方法、主定理求解。遞歸思想:是將大問題分解爲小問題來求解,然後再將小問題分解爲更小的問題。這樣一層一層地分解,直到問題規模被分解得足夠小,不用繼續遞歸分解爲止。這種方法不太嚴謹;有待證明;通常用遞歸樹找出答案,再用代換法來驗證。
歸併排序遞歸樹
#歸併排序僞代碼
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.
對於歸併排序的時間複雜度有:
其中第三步的時間複雜度爲;第二部由於把數組從中間位置一分爲二再歸併,所以爲;所以n大於1 的情形,T(n)就等於第2、3步的時間相加。
先來考慮歸併排序的這種情形,由於隱式函數和cn都是一階的,我們用cn代替: ;其中c爲大於0 的常數;每次分解都是一分爲二,我們把時間上消耗記敘常量O(1). 把它寫成遞歸樹,就相當於是對歸併排序算法第2、3步的逐步二分 次,直到最後都爲葉子節點【時間複雜度爲O(1)】。歸併排序遞歸樹的構造方法如下圖;
現在只需要知道這棵樹的高度h,用高度h 乘以每一層的時間消耗n,就可以得到總的時間複雜度O(n*h)。由於歸併排序是一棵滿二叉樹,那h = log2n ,再由於之前講過的的【對數階的複雜度可以通過換底公式相互轉換,所以用lg表示對數階】 所以粗略估計的歸併排序的時間複雜度就是O(nlogn)。
由上圖可以看出,每一層的運行時間都是cn,葉子層的時間爲.所以總的運行時間爲 .在漸近情況下比要快。所以當輸入規模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,它雖然限制很多但是應用超級方便,
它只適用於形如的遞歸式中:有a個子問題;每個子問題的數據規模是n/b;還要滿足, f(n)要是漸近趨正(asympotically positive)的,這類遞歸樹的畫法規則爲:
(1)每個節點的分支數爲a;
(2) 每層的節點爲T(n) = aT(n / b) + f(n)中的f(n)在當前的n/b下的值。
(3)每層的右側標出當前層中所有節點的和。
下面的主定理給出3種case:
case2在算法導論課中又寫作 :存在某個 ; 使得 ;則 有下式成立;
.
對於case3中 表示下一層的所有值之和。
例題 : 下面的4個遞推式的f(n)不同導致的時間複雜度也不同。
【1】
由於 屬於case1, , 所以有
【2】
由於 屬於case2, , 所以有
【3】
由於 屬於case3, , 所以有
【4】 【不適用master method 】則 .
參考資料: