《數據結構與算法之美》是極客時間上的一個算法學習系列,在學習之后特在此做記錄和總結。
掌握數據結構和算法,不管對於閱讀框架源碼,還是理解其背后的設計思想,都是非常有用的。一旦掌握數據結構和算法,之前可能需要費很大勁兒來優化的代碼,需要花很多心思來設計的架構,用了數據結構和算法之后,很容易就可以解決了。
數據結構和算法是相輔相成的,數據結構是為算法服務的,算法要作用在特定的數據結構之上。
(1)從廣義上講,數據結構就是指一組數據的存儲結構。算法就是操作數據的一組方法。
(2)從狹義上講,是指某些著名的數據結構和算法,比如隊列、棧、堆、二分查找、動態規划等。
該系列總結了 20 個最常用的、最基礎數據結構與算法:
(1)10 個數據結構:數組、鏈表、棧、隊列、散列表、二叉樹、堆、跳表、圖、Trie 樹;
(2)10 個算法:遞歸、排序、二分查找、搜索、哈希算法、貪心算法、分治算法、回溯算法、動態規划、字符串匹配算法。
一、時間復雜度分析
大 O 復雜度表示法,實際上並不具體表示代碼真正的執行時間,而是表示代碼執行時間隨數據規模增長的變化趨勢,所以,也叫作漸進時間復雜度(asymptotic time complexity),簡稱時間復雜度。
分析一段代碼的時間復雜度有三個比較實用的方法:
(1)只關注循環執行次數最多的一段代碼。例如代碼被執行了 n 次,所以總的時間復雜度就是 O(n)。
(2)加法法則:總復雜度等於量級最大的那段代碼的復雜度。例如兩段代碼的復雜度分別為O(n) 和 O(n^2),那么整段代碼的時間復雜度就為 O(n^2)。
(3)乘法法則:嵌套代碼的復雜度等於嵌套內外代碼復雜度的乘積。假設 T1(n) = O(n),T2(n) = O(n^2),則 T1(n) * T2(n) = O(n^3)
二、多項式量級
這些復雜度量級幾乎涵蓋了你今后可以接觸的所有代碼的復雜度量級。

非多項式量級只有兩個:O(2^n) 和 O(n!)。接下來主要來看幾種常見的多項式時間復雜度。
1)O(1)
只是常量級時間復雜度的一種表示方法,並不是指只執行了一行代碼。
只要算法中不存在循環語句、遞歸語句,即使有成千上萬行的代碼,其時間復雜度也是Ο(1)。
2)O(logn)、O(nlogn)
假設代碼的執行次數是2^x=n,那么x=x=log2^n,因此這段代碼的時間復雜度就是 O(log2^n)。

假設有一段代碼的時間復雜度是O(log3^n),而log3^n 就等於 log3^2 * log2^n,所以 O(log3^n) = O(C * log2^n),其中 C=log3^2 是一個常量。
在采用大 O 標記復雜度的時候,可以忽略系數。所以,O(log2^n) 就等於 O(log3^n)。
因此,在對數階時間復雜度的表示方法里,我們忽略對數的“底”,統一表示為 O(logn)。
如果一段代碼的時間復雜度是 O(logn),我們循環執行 n 遍,時間復雜度就是 O(nlogn)。歸並排序、快速排序的時間復雜度都是 O(nlogn)。
3)O(m+n)、O(m*n)
m 和 n 是表示兩個數據規模。
因為無法事先評估 m 和 n 誰的量級大,所以在表示復雜度的時候,就不能簡單地利用加法法則,省略掉其中一個。
三、空間復雜度分析
時間復雜度的全稱是漸進時間復雜度,表示算法的執行時間與數據規模之間的增長關系。
類比一下,空間復雜度全稱就是漸進空間復雜度(asymptotic space complexity),表示算法的存儲空間與數據規模之間的增長關系。
常見的空間復雜度就是 O(1)、O(n)、O(n2 ),像 O(logn)、O(nlogn) 這樣的對數階復雜度平時都用不到。

四、最好、最壞、平均、均攤時間復雜度
1)最好和最壞
最好情況時間復雜度就是,在最理想的情況下,執行這段代碼的時間復雜度。例如在最理想的情況下,要查找的變量 x 正好是數組的第一個元素。
最壞情況時間復雜度就是,在最糟糕的情況下,執行這段代碼的時間復雜度。如果數組中沒有要查找的變量 x,我們需要把整個數組都遍歷一遍才行。
2)平均情況時間復雜度
要查找的變量 x,要么在數組里,要么就不在數組里。為了方便理解,假設在數組中與不在數組中的概率都為 1/2。
另外,要查找的數據出現在 0~n-1 這 n 個位置的概率也是一樣的,為 1/n。
所以,根據概率乘法法則,要查找的數據出現在 0~n-1 中任意位置的概率就是 1/(2n)。
把每種情況發生的概率也考慮進去,那平均時間復雜度的計算過程就變成了這樣:

這個值就是概率論中的加權平均值,也叫作期望值,所以平均時間復雜度的全稱應該叫加權平均時間復雜度或者期望時間復雜度。
時間復雜度的大 O 標記法中,可以省略掉系數、低階、常量,這段代碼的加權平均時間復雜度仍然是 O(n)。
