資料結構和演算法-學習筆記(一)

清水之靈發表於2018-10-19

複雜度分析

資料結構和演算法本身解決的就是“快”和“省”的問題,如何讓程式碼執行的更快且佔用更少的儲存空間。所以你設計的資料結構和演算法的好壞的衡量標準是什麼呢?——時間和空間複雜度。

時間、空間複雜度分析

有很多程式碼監控和統計工具能得到演算法的執行時間和佔用記憶體大小,為什麼還要人工去分析複雜度? ——有侷限性。

  1. 得出的測試結果依賴測試環境(CPU、GPU等);
  2. 測試結果受資料規模的影響。 所以我們需要一個不受影響的分析方法。

大O複雜度表示法:

粗略假設每行程式碼的執行時間是一定的為unit_time(實際上每行程式碼對應的CPU執行個數、時間都不一樣)。一段程式碼的時間複雜度就可以看成是這段程式碼需要執行的總行數。 

資料結構和演算法-學習筆記(一)
2、3行都是1個unit_time,4、5行程式碼都迴圈執行了n次所以是2n個unit_time。因此總的執行時間應該是 T =(2+2n)unit_time = 總執行次數 * unit_time;所以程式碼的總執行時間和總執行次數成正比。
資料結構和演算法-學習筆記(一)
下面直接按照執行次數來計算 2、3、4行都執行了1次,5、6行都執行了n次,7、8行都執行了n
n次。因此T=(2nn+2n+3)*unit_time

總結規律的公式: 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);

時間複雜度分析方法:

  1. 只關注迴圈執行次數最多的一段程式碼:上面剛說了,時間複雜度只需要關心最大階的量級就行了。因此在分析一段程式碼的時間複雜度時,只關注迴圈執行次數(最大階)最多的那一段程式碼就行了。這段核心程式碼執行次數的n的量級就是這段程式碼的時間複雜度了。
  2. 加法法則:總複雜度等於量級最大的那段程式碼的複雜度。幾段程式碼肯定都有自己的執行次數,他們加起來的總的時間複雜度就取量級最大的那段程式碼的時間複雜度。
  3. 乘法法則:巢狀程式碼(一段程式碼中嵌入了另一段程式碼)的複雜度等於巢狀內外程式碼複雜度的乘積。
    資料結構和演算法-學習筆記(一)
    T(n) = T1(n)*T2(n) = O(n……2)

幾種常見的時間複雜度量級

資料結構和演算法-學習筆記(一)
可以總結為兩類:多項式量級和非多項式量級(只有兩個O(2^n)和O(n!)) 我們把時間複雜度為非多項式量級的演算法問題叫做NP(非確定多項式)問題。 當資料規模n越來越大時,非多項式量級演算法的執行時間會急劇增加,求解問題的執行時間會無限增長,因次這種複雜度的演算法是非常低效的演算法。

  1. O(1) 表示常量級時間複雜度,不是隻執行一次。一般只要演算法中沒有迴圈和遞迴等語句,即使有成千上萬行程式碼,時間複雜度也是O(1)。

  2. 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次。比如歸併排序、快速排序的時間複雜度。

  3. 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)。儘可能讓程式碼高效。

最好、最壞情況下時間複雜度

資料結構和演算法-學習筆記(一)
x可能出現在任意位置,時間複雜度不定。為了表示程式碼在不同情況下的時間複雜度:最好情況時間複雜度、最壞情況時間複雜度、平均情況時間複雜度。 最好最壞都是極端情況下的複雜度,發生概率不大。平均時間複雜度更常用。 一種演算法:各種可能的時間複雜度和/總可能數 = 平均情況的時間複雜度 O(n)

資料結構和演算法-學習筆記(一)
然因為每種情況的出現概率不一定相同,所以?這種方法不準確。

平均時間複雜度

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

資料結構和演算法-學習筆記(一)

均攤時間複雜度

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

資料結構和演算法-學習筆記(一)
?的例子,如果用平均複雜度得出O(1)。但是上面的程式碼和前面平均複雜度程式碼的不同之處:find函式有極端情況,但insert大部分情況都是O(1),而且出現的頻率和順序都是有規律的。 這時可以用更簡單的分析方法:攤還分析法。通過這種方法得到的時間複雜度叫均攤時間複雜度。 上面的例子前面n次的時間複雜度都是O(1),接下來的n+1次的複雜度是O(n),因此把耗時多的n+1此操作使用的時間均攤到前面n次中,則所有的時間複雜度就都是常量級的。1的次數遠大於n了,憑感覺也就是1了。

根據經驗,均攤時間複雜度一般都等於最好情況的時間複雜度(把最壞的時間複雜度均攤到大多數情況下)

均攤就是特殊的平均了,均攤還是平均,要看具體的分析。

非遞迴程式碼的多項式求和技巧

資料結構和演算法-學習筆記(一)

遞迴程式碼

從原始遞推方程開始,反覆將對於遞推方程左邊的函式用右邊的等式代入,直到 得到初值,然後將所得的結果進行化簡。

例如在呼叫歸併排序mergeSort(a,0,n-1)對陣列a[0...n−1] 排序時,執行時間T(n) 的遞推關係式為:

T ( n ) = { O ( 1 ) n = 1 T ( n 2 ) + T ( n 2 ) + O ( n ) n >= 2

其中,O(n) 為merge()所需要的時間,設為cn (c為正常量)。因此:

T ( n ) = 2 T ( n 2 ) + c n = 2 ( 2 T ( n 4 ) + c n 2 ) + c n = 2 2 T ( n 4 ) + 2 c n = 2 3 T ( n 8 ) + 3 c n = . . . = 2 k T ( n 2 k ) + k c n = n O ( 1 ) + c n log 2 n = O ( n log 2 n ) , ( n = 2 k , k = log 2 n )

時間複雜度僅是個粗略的計算,因此我們可以忽略一些細節,如假設問題規模n=2……k,從而得出時間複雜度。

相關文章