第2課:算法復(fù)雜度分析(上):時間、空間復(fù)雜度分析法

1、算法的考量指標(biāo)

算法的考量指標(biāo),我們是用時間、空間復(fù)雜度來衡量的。

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

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

我把代碼跑一遍,通過統(tǒng)計、監(jiān)控,就能得到算法執(zhí)行的時間和占用的內(nèi)存大小
這種評估算法執(zhí)行效率的方法是正確的。很多數(shù)據(jù)結(jié)構(gòu)和算法書籍還給這種方法起了一個名字,叫事后統(tǒng)計法。但是,這種統(tǒng)計方法有非常大的局限性。
  • 1.測試結(jié)果非常依賴測試環(huán)境測試環(huán)境中硬件的不同會對測試結(jié)果有很大的影響
    比如,我們拿同樣一段代碼,分別用Intel Core i9處理器和 Intel Core i3 處理器來運(yùn)行,不用說,i9 處理器要比 i3 處理器執(zhí)行的速度快很多。還有,比如原本在這臺機(jī)器上 a 代碼執(zhí)行的速度比 b 代碼要快,等我們換到另一臺機(jī)器上時,可能會有截然相反的結(jié)果。

  • 2.測試結(jié)果受數(shù)據(jù)規(guī)模的影響很大
    后面我們會講排序算法,我們先拿它舉個例子。對同一個排序算法,待排序數(shù)據(jù)的有序度不一樣,排序的執(zhí)行時間就會有很大的差別。極端情況下,如果數(shù)據(jù)已經(jīng)是有序的,那排序算法不需要做任何操作,執(zhí)行時間就會非常短。除此之外,如果測試數(shù)據(jù)規(guī)模太小,測試結(jié)果可能無法真實地反應(yīng)算法的性能。比如,對于小規(guī)模的數(shù)據(jù)排序,插入排序可能反倒會比快速排序要快!所以,我們需要一個不用具體的測試數(shù)據(jù)來測試,就可以粗略地估計算法的執(zhí)行效率的方法。這就是我們今天要講的時間、空間復(fù)雜度分析方法。

3、大O表示法

大O表示法:算法的時間復(fù)雜度通常用大O符號表述,定義為T[n] = O(f(n))。稱函數(shù)T(n)以f(n)為界或者稱T(n)受限于f(n)。 如果一個問題的規(guī)模是n,解這一問題的某一算法所需要的時間為T(n)。T(n)稱為這一算法的“時間復(fù)雜度”。當(dāng)輸入量n逐漸加大時,時間復(fù)雜度的極限情形稱為算法的“漸近時間復(fù)雜度”。

#例1 int sum(int n) {
   int result = 0;
   int i = 1;
   for (; i <= n; ++i) {
     result = result + i;
   }
   return result;
 }

分析:假設(shè)每行代碼執(zhí)行的時間都一樣,為 unit_time。
第 2、3 行代碼分別需要 1 個 unit_time 的執(zhí)行時間,第 4、5 行都運(yùn)行了 n 遍,
所以需要 2n * unit_time 的執(zhí)行時間,所以這段代碼總的執(zhí)行時間就是 (2n+2) * unit_time。
可以看出來,所有代碼的執(zhí)行時間 T(n) 與每行代碼的執(zhí)行次數(shù)成正比。
再看看下面這個例子,同上面的分析方法,我們得出這段代碼總的執(zhí)行時間就是 (2n^2+2n+3)*unit_time。

例2:int sum(int n) {
   int result = 0;
   int i = 1;
   int j = 1;
   for (; i <= n; ++i) {
     j = 1;
     for (; j <= n; ++j) {
       result = result +  i * j;
     }
   }
 }

把這個規(guī)律總結(jié)成一個公式:T(n) = O(f(n))

4、如何分析一段代碼的時間復(fù)雜度?

  • 只關(guān)注循環(huán)執(zhí)行次數(shù)最多的一段代碼
    \color{red}{我們通常會忽略掉公式中的常量、低階、系數(shù),只需要記錄一個最大階的量級就可以了。}
    例子1中:其中第 2、3、 行代碼都是常量級的執(zhí)行時間,與 n 的大小無關(guān),所以對于復(fù)雜度并沒有影響。
    4、5行代碼循環(huán)執(zhí)行,與 n 的大小無關(guān),次數(shù)最多的是第4、5行代碼,所以這塊代碼要重點(diǎn)分析。前面我們也講過,這兩行代碼被執(zhí)行了 n 次,所以總的時間復(fù)雜度就是 O(n)。
    例子2中時間復(fù)雜度為:O(n^2)

  • 加法法則:總復(fù)雜度等于量級最大的那段代碼的復(fù)雜度

例3:int sum(int n) {
   int result_1 = 0;
   int x = 1;
   for (; x < 1000; ++x) {
     result_1 = result_1 + x;
   }

   int result_2 = 0;
   int y = 1;
   for (; y < n; ++y) {
     result_2 = result_2 + y;
   }
 
   int result_3 = 0;
   int i = 1;
   int j = 1;
   for (; i <= n; ++i) {
     j = 1; 
     for (; j <= n; ++j) {
       result_3 = result_3 +  i * j;
     }
   }
   return result_1 + result_2 + result_3;
 }

這個例子分三部分:求result_1、result_2、result_3。
第一部分跟n沒關(guān)系:屬于常量階,我們表示為0(1)
第二部分:O(n)
第三部分為:O(n^2)
所以整個sum函數(shù)的時間復(fù)雜度為:T(n)=O(1)+T1(n)+T2(n)=max(O(f(n)), O(g(n)))=max(O(n),O(n^2)) = n^2

  • 乘法法則:嵌套代碼的復(fù)雜度等于嵌套內(nèi)外代碼復(fù)雜度的乘積
    如果 T1(n)=O(f(n)),T2(n)=O(g(n)),
    那么 T(n)=T1(n)T2(n)=O(f(n))O(g(n))=O(f(n)*g(n)).
    也就是說,假設(shè) T1(n) = O(n),T2(n) = O(n2),則 T1(n) * T2(n) = O(n3)
例4: int sum1(int n) {
   int result = 0;
   int i = 1;
   for (; i <= n; ++i) {
     result = sum(i) + i;
   }
   return result;
 }
 
int sum(int n) {
   int result = 0;
   int i = 1;
   int j = 1;
   for (; i <= n; ++i) {
     j = 1;
     for (; j <= n; ++j) {
       result = result +  i * j;
     }
   }
 }

5、常見時間復(fù)雜度分析

常見時間復(fù)雜度.png
  • O(m+n)、O(mn)
    代碼的復(fù)雜度由兩個數(shù)據(jù)的規(guī)模來決定,如例5:m 和 n 是表示兩個數(shù)據(jù)規(guī)模。我們無法事先評估 m 和 n 誰的量級大,所以我們在表示復(fù)雜度的時候,就不能簡單地利用加法法則,省略掉其中一個。所以,上面代碼的時間復(fù)雜度就是 O(m+n)。針對這種情況,原來的加法法則就不正確了,我們需要將加法規(guī)則改為:T1(m) + T2(n) = O(f(m) + g(n))。但是乘法法則繼續(xù)有效:T1(m)
    T2(n) = O(f(m) * f(n))。
例5: int cal(int m, int n) {
  int sum_1 = 0;
  int i = 1;
  for (; i < m; ++i) {
    sum_1 = sum_1 + i;
  }

  int sum_2 = 0;
  int j = 1;
  for (; j < n; ++j) {
    sum_2 = sum_2 + j;
  }

  return sum_1 + sum_2;
}

6、空間復(fù)雜度

空間復(fù)雜度比較簡單,空間復(fù)雜度(Space Complexity)是對一個算法在運(yùn)行過程中臨時占用存儲空間大小的量度,記做S(n)=O(f(n))??匆粋€例子。

例6: int sum(int n) {
  int result = 0;
  int[] a = new int[n]; // 開辟了新的存儲空間
  for (i; i <n; ++i) {
    a[i] = i * i;
  }
  for (i = n-1; i >= 0; --i) {
    result +=a[i];
  }
  return result;
}

除了第三行申請了一個大小為 n 的 int 類型數(shù)組,除此之外,剩下的代碼都沒有占用更多的空間,所以整段代碼的空間復(fù)雜度就是 O(n)。
我們常見的空間復(fù)雜度就是 O(1)、O(n)、O(n2),像 O(logn)、O(nlogn) 這樣的對數(shù)階復(fù)雜度平時都用不到。而且,空間復(fù)雜度分析比時間復(fù)雜度分析要簡單很多。所以,對于空間復(fù)雜度,掌握剛我說的這些內(nèi)容已經(jīng)足夠了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容