遞歸樹
遞歸的思想就是,將大問題分解爲小問題來求解,然後再將小問題分解爲小小問題。
這樣一層一層地分解,直到問題的數據規模被分解得足夠小,不用繼續遞歸分解爲止。
如果我們把這個一層一層的分解過程畫成圖,它其實就是一棵樹。我們給這棵樹起一個名字,叫作遞歸樹。
遞歸代碼的時間複雜度分析起來很麻煩,可以利用遞推公式來求解算法的時間複雜度,如求解歸併排序、快速排序的時間複雜度
但是些情況,比如快排的平均時間複雜度的分析,用遞推公式的話,會涉及非常複雜的數學推導。
此時我們就可以藉助遞歸樹來分析遞歸實現算法的時間複雜度
如何用遞歸樹求解時間複雜度
遞歸樹分析歸併排序時間複雜度
歸併排序每次會將數據一分爲二,因爲每次分解都是一分爲二,所以代價很低,我們把時間上的消耗記作常量 1。
歸併算法中比較耗時的是歸併操作,也就是把兩個子數組合併爲大數組。
每一層歸併操作消耗的時間總和是一樣的,跟要排序的數據規模有關。把每一層歸併操作消耗的時間記作 n。
那麼只需要知道這棵樹的高度 h,用高度 h 乘以每一層的時間消耗 n,就可以得到總的時間複雜度 O(n∗h)。
從歸併排序的原理可以看出來,歸併排序的遞歸樹是一棵滿二叉樹,而滿二叉樹的高度大約是log n 。
所以,歸併排序遞歸實現的時間複雜度就是 O(nlogn)。
當然,除了用遞歸樹來分析歸併排序遞歸實現的時間複雜度,還可以利用遞推公式來分析
具體方法在我的歸併排序分析裏面有:https://blog.csdn.net/qq_42006733/article/details/104415767
遞歸樹分析快速排序時間複雜度
關於利用遞推公式來分析遞歸實現算法的時間複雜度對於快速排序來說就相對於複雜。
快速排序在最好情況下,分區點選擇及其合理,每次分區都能一分爲二的時候。
這個時候用遞推公式 T(n)=2T(n/2)+n,很容易就能推導出時間複雜度是 O(nlogn)。
但是,並不可能每次分區都這麼幸運,正好一分爲二。
假設平均情況下,每次分區之後,兩個分區的大小比例爲 1:k。
當 k=9 時,如果用遞推公式的方法來求解時間複雜度的話,遞推公式就寫成 T(n)=T(n/10)+T(9*n/10)+n。
利用遞推公式來分析時間複雜度也是可以的,但是推導過程就很複雜。
在這種情況下利用遞歸樹就簡單的很多。
仍假設k=9 的時候,快速排序的過程中,每次分區都要遍歷待分區區間的所有數據 。
跟歸併排序一樣,每一層分區操作所遍歷的數據的個數之和就是 n。
我們現在只要求出遞歸樹的高度 h,這個快排過程遍歷的數據個數就是 h∗n ,也就是說,時間複雜度就是 O(h∗n)。
但是歸併排序中每次分區都是一分爲二,所以歸併排序的遞歸樹是滿二叉樹。高度爲log n
而快速排序每次分區並不是均勻地一分爲二,所以遞歸樹並不是滿二叉樹。高度就不是log n 了。
歸併排序中,開始合併的條件是數據已經被劃分爲單個的了,而快速排序結束的條件就是待排序的小區間,大小爲 1。
那麼就是說葉子節點裏的數據規模是 1。而根節點是n。
那麼遞歸樹中最短的一個路徑每次都乘以 1/10,最長的一個路徑每次都乘以 9/10 。
遍歷數據的個數總和就介於 nlog10n 和 nlog910n 之間。
根據複雜度的大 O 表示法,對數複雜度的底數不管是多少,我們統一寫成 log n
當分區大小比例是 1:9 時,快速排序的時間複雜度仍然是 O(nlogn)。
若k = 99,也就是說每次的分區更加不平均了,還是類似的方法,求的樹的最短路徑就是 log100 n,最長路徑是 log 99/100 n
只是底數變了,但是時間複雜度也仍然是 O(nlogn)。
不管k的值是多少,甚至是 999,9999,只要 k 的值不隨 n 變化,是一個事先確定的常量,那快排的時間複雜度就是 O(nlogn)。
所以說快排的平均時間複雜度就是 O(nlogn)。
遞歸樹分析斐波那契數列時間複雜度
斐波那契數列就是經典的遞推,F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 3,n ∈ N*)
用代碼表示就是
int f(int n) {
if (n == 1) return 1;
if (n == 2) return 2;
return 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,第三層的總時間消耗就是 22。
依次類推,第 k 層的時間消耗就是 2^k−1
如果路徑長度都爲 n,那這個總和就是 2^n−1。
如果路徑長度都是 2n ,那整個算法的總的時間消耗就是 2^(n/2)−1。
所以斐波那契數列算法的時間複雜度就介於 O(2^n) 和 O(2^(n/2)) 之間。
雖然這樣得到的結果只是一個範圍,但是已經可以明確這個算法的時間複雜度是指數級的,非常之高。
遞歸樹分析全排列時間複雜度
全排列問題:如何把 n 個數據的所有排列都找出來。
比如,1,2,3 這樣 3 個數據,有下面這幾種不同的排列:
1, 2, 3
1, 3, 2
2, 1, 3
2, 3, 1
3, 1, 2
3, 2, 1
全排列問題可以用遞歸來實現。
如果我們確定了最後一位數據,那就變成了求解剩下 n−1 個數據的排列問題。
而最後一位數據可以是 n 個數據中的任意一個,因此它的取值就有 n 種情況。
所以,“n 個數據的排列”問題,就可以分解成 n 個“n−1 個數據的排列”的子問題。
寫成遞推公式
假設數組中存儲的是1,2, 3...n。
f(1,2,...n) = {最後一位是1, f(n-1)} + {最後一位是2, f(n-1)} +...+{最後一位是n, f(n-1)}。