數(shù)據(jù)結(jié)構(gòu)與算法之美(一):復(fù)雜度分析

本章內(nèi)容源于筆者對極客時間《數(shù)據(jù)結(jié)構(gòu)與算法之美》以下章節(jié)的學(xué)習(xí)筆記:

復(fù)雜度分析是整個算法學(xué)習(xí)的精髓,只要掌握了它,數(shù)據(jù)結(jié)構(gòu)和算法的內(nèi)容基本上就掌握了一半。

為什么需要復(fù)雜度分析

把代碼跑一遍,通過統(tǒng)計、監(jiān)控得到算法執(zhí)行的時間和占用的內(nèi)存大小,這種方法稱為事后統(tǒng)計法,有非常大的局限性:

  • 1.測試結(jié)果非常依賴測試環(huán)境
  • 2.測試結(jié)果受數(shù)據(jù)規(guī)模的影響很大

我們需要一個不用關(guān)心的宿主環(huán)境、不用具體的測試數(shù)據(jù)來測試,就可以粗略地估計算法的執(zhí)行效率的方法。就是時間、空間復(fù)雜度分析法。

大O復(fù)雜度表示法

假設(shè)每行代碼執(zhí)行的時間都一樣,為單位時間,所有代碼的執(zhí)行時間T(n)與每行代碼的執(zhí)行次數(shù)成正比。我們把這個規(guī)律總結(jié)成一個公式:T(n) = O(f(n))

  • T(n)表示代碼執(zhí)行的時間
  • f(n)表示每行代碼執(zhí)行的次數(shù)總和
  • n表示數(shù)據(jù)規(guī)模的大小

這就是大O時間復(fù)雜度表示法

  • 注意:大O時間復(fù)雜度實際上并不代表真正的執(zhí)行時間,表示代碼執(zhí)行時間隨數(shù)據(jù)規(guī)模增長的變化趨勢。公式中的低階、常量、系數(shù)三部分并不左右增長趨勢,所以忽略。

時間復(fù)雜度分析

時間復(fù)雜度全稱漸進時間復(fù)雜度。分析技巧如下:

  • 1.只關(guān)注循環(huán)執(zhí)行次數(shù)最多的一段代碼
  • 2.加法法則:總復(fù)雜度等于量級最大的那段代碼的復(fù)雜度
  • 3.乘法法則:嵌套代碼的復(fù)雜度等于嵌套內(nèi)外代碼復(fù)雜度的乘積

強調(diào)一下,即便一段段代碼循環(huán)10000次、100000次,只要是一個已知的數(shù),跟n無關(guān),照樣也是常量級的執(zhí)行時間。當n無限大的時候,就可以忽略。

以上三種復(fù)雜度的分析技巧不用刻意去記憶,實際上,復(fù)雜度分析這個東西關(guān)鍵在于“熟練”,多看案例多分析。

幾種常見時間復(fù)雜度實例分析

常見復(fù)雜度量級并不多,以下幾乎涵蓋了今后接觸的所有代碼的復(fù)雜度量級。

復(fù)雜度量級 大O表達式 所屬分類
常量階 O(1) 多項式量級
對數(shù)階 O(logn) 多項式量級
線性階 O(n) 多項式量級
線性對數(shù)階 O(nlogn) 多項式量級
平方階 O(n2) 多項式量級
立方階 O(n3) 多項式量級
k次方階 O(nk) 多項式量級
指數(shù)階 O(2n) 非多項式量級
階乘階 O(n!) 非多項式量級

O(1)

只要代碼的執(zhí)行時間不隨n的增大而增長,時間復(fù)雜度都記作O(1)。一般情況下,只要算法中不存在循環(huán)語句、遞歸語句,即使有成千上萬行的代碼,其時間復(fù)雜度也是Ο(1)

O(logn)、O(nlog)

對數(shù)階時間復(fù)雜度非常常見,同時也是最難分析的一種時間復(fù)雜度。

i = 1;
while (i <= n) {
    i = i * 2;
}

以上代碼中可以看出,變量i的值從1開始取,每循環(huán)一次就乘以 2,當大于n時,循環(huán)結(jié)束。i的取值是一個等比數(shù)列,求解2x = n,x = log2n。

類似的,log3n = log32 * log2n,不管是以2、3甚至是10為底,都可以轉(zhuǎn)換為c * log2n,忽略系數(shù)和對數(shù)的“底”,都記作O(logn)

如果一段代碼的時間復(fù)雜度是 O(logn) ,循環(huán)執(zhí)行n遍,時間復(fù)雜度就是 O(nlogn) 了。

O(nlogn) 也是一種非常常見的算法時間復(fù)雜度。比如,歸并排序、快速排序的時間復(fù)雜度都是 O(nlogn)

O(m+n)、O(m*n)

代碼的復(fù)雜度由兩個數(shù)據(jù)的規(guī)模來決定。

空間復(fù)雜度分析

空間復(fù)雜度全稱漸進空間復(fù)雜度,表示算法的存儲空間與數(shù)據(jù)規(guī)模之間的增長關(guān)系

空間復(fù)雜度分析比時間復(fù)雜度分析要簡單很多。常見的空間復(fù)雜度就是O(1)O(n)O(n2),像 O(logn)O(nlogn) 這樣的對數(shù)階復(fù)雜度平時都用不到。

小結(jié):復(fù)雜度包括時間復(fù)雜度和空間復(fù)雜度,用來分析算法執(zhí)行效率與數(shù)據(jù)規(guī)模之間的增長關(guān)系。越高階復(fù)雜度的算法執(zhí)行效率越低。常見復(fù)雜度從低階到高階有:
O(1)O(logn)O(n)O(nlogn)O(n2 )

復(fù)雜度分析的4個概念

  • 最好情況時間復(fù)雜度:代碼在最理想情況下執(zhí)行的時間復(fù)雜度。
  • 最壞情況時間復(fù)雜度:代碼在最糟糕情況下執(zhí)行的時間復(fù)雜度。
  • 平均情況時間復(fù)雜度:引入概率的概念,代碼在所有情況下執(zhí)行的次數(shù)的加權(quán)平均值。

實際上,在大多數(shù)情況下并不需要區(qū)分最好、最壞、平均情況時間復(fù)雜度。只有同一塊代碼在不同的情況下,時間復(fù)雜度有量級的差距,才會使用這三種復(fù)雜度表示法來區(qū)分。

  • 均攤時間復(fù)雜度:利用攤還分析法將個別情況的高復(fù)雜度均攤到大部分情況的低復(fù)雜度所得到的時間復(fù)雜度。

應(yīng)用場景:對一個數(shù)據(jù)結(jié)構(gòu)進行一組連續(xù)操作中,大部分情況下時間復(fù)雜度都很低,只有個別情況下時間復(fù)雜度比較高,而且這些操作之間存在前后連貫的時序關(guān)系,這個時候就可以將這一組操作放在一塊兒分析,看是否能將較高時間復(fù)雜度那次操作的耗時,平攤到其他那些時間復(fù)雜度比較低的操作上。

  • 一般均攤時間復(fù)雜度就等于最好情況時間復(fù)雜度。
  • 均攤時間復(fù)雜度就是一種特殊的平均時間復(fù)雜度。

小結(jié):引入最好情況時間復(fù)雜度、最壞情況時間復(fù)雜度、平均情況時間復(fù)雜度、均攤時間復(fù)雜度這幾個概念是因為同一段代碼在不同輸入情況下,復(fù)雜度量級有可能不一樣,通過比較分析,我們可以更加全面地表示一段代碼的執(zhí)行效率。

思考題一:有人說,我們項目之前都會進行性能測試,再做代碼的時間復(fù)雜度、空間復(fù)雜度分析,是不是多此一舉呢?而且,每段代碼都分析一下時間復(fù)雜度、空間復(fù)雜度,是不是很浪費時間呢?你怎么看待這個問題呢?

參考回答

  • 1.復(fù)雜度分析是一個理論分析,與宿主無關(guān),能讓程序員在寫代碼時對算法的執(zhí)行效率有個大致認識,從而寫出效率更高的程序;
  • 2.通過練習(xí)就能達到熟練地看出是否浪費時間,比如復(fù)雜度越低階效率越高,為代碼質(zhì)量考慮并不算浪費時間。

思考題二:分析下面這個 add() 函數(shù)的時間復(fù)雜度。

// 全局變量,大小為 10 的數(shù)組 array,長度 len,下標 i
int array[] = new int[10]; 
int len = 10;
int i = 0;

// 往數(shù)組中添加一個元素
void add(int element) {
   if (i >= len) { // 數(shù)組空間不夠了
     // 重新申請一個2倍大小的數(shù)組空間
     int new_array[] = new int[len*2];
     // 把原來array數(shù)組中的數(shù)據(jù)依次copy到new_array
     for (int j = 0; j < len; ++j) {
       new_array[j] = array[j];
     }
     // new_array 復(fù)制給 array,array 現(xiàn)在大小就是 2 倍 len 了
     array = new_array;
     len = 2 * len;
   }
   // 將 element 放到下標為 i 的位置,下標 i 加一
   array[i] = element;
   ++i;
}

參考回答

以上代碼實現(xiàn)往一個數(shù)組中添加數(shù)據(jù),要是數(shù)組放不下了就將數(shù)組擴大到原來2倍,然后再添加新元素。最壞情況時間復(fù)雜度是 O(n),最好情況時間復(fù)雜度、平均情況時間復(fù)雜度、均攤時間復(fù)雜度都是 O(1)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容