複雜度分析
資料結構和演算法本身解決的就是“快”和“省”的問題,如何讓程式碼執行的更快且佔用更少的儲存空間。所以你設計的資料結構和演算法的好壞的衡量標準是什麼呢?——時間和空間複雜度。
時間、空間複雜度分析
有很多程式碼監控和統計工具能得到演算法的執行時間和佔用記憶體大小,為什麼還要人工去分析複雜度? ——有侷限性。
- 得出的測試結果依賴測試環境(CPU、GPU等);
- 測試結果受資料規模的影響。 所以我們需要一個不受影響的分析方法。
大O複雜度表示法:
粗略假設每行程式碼的執行時間是一定的為unit_time(實際上每行程式碼對應的CPU執行個數、時間都不一樣)。一段程式碼的時間複雜度就可以看成是這段程式碼需要執行的總行數。 


總結規律的公式: T(n) = O(f(n)) 表示總執行時間和總執行次數成正比。 T(n)表示總執行時間 N表示資料規模的大小 f(n)表示所有程式碼的執行總次數,因為它是一個公式所以用f(n)表示 O表示正比關係
所以: 例1 :T(n) = O(2+2n) 例2:T(n) = O(2*n^2+2n+3)
以上就是大O時間複雜度表示法。 可以看出大O時間複雜度表示法並不表示程式碼真正的執行時間,而是表示程式碼執行時間隨著總執行次數(也可以說是資料規模n)增長的變化趨勢,因此也叫漸進式時間複雜度。
當n很大時,f(n)中的低階2n、常量3、係數2三部分並不會影響總執行次數的增長趨勢的,所以可以忽略,只需要記錄一個最大量級就可以估算出總執行時間了。因此上邊例子中的大O複雜度可以記為:T(n) = O(n); T(n) = O(n^2);
時間複雜度分析方法:
- 只關注迴圈執行次數最多的一段程式碼:上面剛說了,時間複雜度只需要關心最大階的量級就行了。因此在分析一段程式碼的時間複雜度時,只關注迴圈執行次數(最大階)最多的那一段程式碼就行了。這段核心程式碼執行次數的n的量級就是這段程式碼的時間複雜度了。
- 加法法則:總複雜度等於量級最大的那段程式碼的複雜度。幾段程式碼肯定都有自己的執行次數,他們加起來的總的時間複雜度就取量級最大的那段程式碼的時間複雜度。
- 乘法法則:巢狀程式碼(一段程式碼中嵌入了另一段程式碼)的複雜度等於巢狀內外程式碼複雜度的乘積。
T(n) = T1(n)*T2(n) = O(n……2)
幾種常見的時間複雜度量級

-
O(1) 表示常量級時間複雜度,不是隻執行一次。一般只要演算法中沒有迴圈和遞迴等語句,即使有成千上萬行程式碼,時間複雜度也是O(1)。
-
O(logn)和O(nlogn)對數階時間複雜度很常見,
I的值就是一個等比數列: 因此3行程式碼執行了x次, 2^x = n因此 x = log2n 時間複雜度O(log2n) 同理如下: 時間複雜度O(log3n)不管以2為底、3為底還是10為底,統一把所有的對數階的時間複雜度記為O(logn)。為什麼呢?因為對數之間是可以相互轉化的 log3n = log32 * log2n 去掉常量複雜度是一樣的。因此忽略底統一O(logn)。 O(nlogn)根據乘法法則,對時間複雜度是logn的程式碼執行n次。比如歸併排序、快速排序的時間複雜度。 -
O(m+n)和O(m*n)程式碼的時間複雜度由兩個資料規模決定。
按照加法法則應該是在 m和n中找量級最大的作為整個程式碼的複雜度。但是m和n無法確定誰大,這是就不能簡單的利用加法法則了,它的複雜度就是加和了O(m+n)。但是乘法法則仍適用。
空間複雜度分析
同理時間複雜度,空間複雜度也叫漸進式空間複雜度,表示演算法所用的儲存空間和資料規模之間的增長關係。空間複雜度就和程式碼中所佔儲存空間總位元組數(簡單的可以看成是塊數)成正比了。同樣找最大量級。注:是指除了原本的資料儲存空間外,演算法執行還需要的額外儲存空間。如作為引數傳入的大小為n的陣列,是不算在內的。

第2行申請了一個常量的儲存空間,3行申請了n個儲存空間之後僅是使用沒有佔用更多空間。同時間複雜度得出它的空間複雜度O(n)。
比較常見的空間複雜度就是:O(1) 、O(n) 、O(n^2)
複雜度效率比較

從低階到高階:O(1) O(logn) O(n) O(nlogn) O(n^2) 如上圖可看出一般越高階效率越低。
計算時間空間複雜度的好處:儘可能從程式碼級別、根本上、理論上讓保證程式碼的執行效率,並不是一定能確保在不同環境下都是O(1)的效率就一定優於O(n)。儘可能讓程式碼高效。
最好、最壞情況下時間複雜度


平均時間複雜度
也叫甲醛平均時間複雜度或期望時間複雜度,因為對於每一種可能都乘上了它出現的概率。 複雜度是1、2……n的概率是1/2n(在與不在陣列內的概率都是是1/2,而在陣列內的哪個位置上的概率都是一樣的也就是1/n,因此匹配上的概率就是1/21/n=1/2n)。所以每種情況的時間複雜度她出現的概率=平均時間複雜度。O(n)

均攤時間複雜度
?的時間複雜度演算法僅有在不確定複雜度的時候才使用,均攤的使用場景會更特殊有限。

根據經驗,均攤時間複雜度一般都等於最好情況的時間複雜度(把最壞的時間複雜度均攤到大多數情況下)
均攤就是特殊的平均了,均攤還是平均,要看具體的分析。
非遞迴程式碼的多項式求和技巧

遞迴程式碼
從原始遞推方程開始,反覆將對於遞推方程左邊的函式用右邊的等式代入,直到 得到初值,然後將所得的結果進行化簡。
例如在呼叫歸併排序mergeSort(a,0,n-1)對陣列a[0...n−1] 排序時,執行時間T(n) 的遞推關係式為:
其中,O(n) 為merge()所需要的時間,設為cn (c為正常量)。因此:
時間複雜度僅是個粗略的計算,因此我們可以忽略一些細節,如假設問題規模n=2……k,從而得出時間複雜度。