1. 程式人生 > 其它 >時間複雜度介紹

時間複雜度介紹

技術標籤:演算法學習演算法資料結構

時間複雜度

O ( 1 ) O(1) O(1)

首先你必須明確一個概念, O ( 1 ) O(1) O(1) 只是常量級時間複雜度的一種表示方法,並不是指只執行了一行程式碼。比如這段程式碼,即便有 3 行,它的時間複雜度也是 O ( 1 ) O(1) O(1,而不是 O ( 3 ) O(3) O(3)

int i = 8;
int j = 6; 
int sum = i + j;

只要程式碼的執行時間不隨 n 的增大而增長,這樣程式碼的時間複雜度我們都記作 O(1)。或者說,一般情況下,只要演算法中不存在迴圈語句、遞迴語句,即使有成千上萬行的程式碼,其時間複雜度也是Ο(1)。

O ( l o g n ) O(logn) O(logn) O ( n l o g n ) O(nlogn) O(nlogn)

對數階時間複雜度非常常見,同時也是最難分析的一種時間複雜度。我通過一個例子來說明一下。

i=1;

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

根據我們前面講的複雜度分析方法,第三行程式碼是迴圈執行次數最多的。所以,我們只要能計算出這行程式碼被執行了多少次,就能知道整段程式碼的時間複雜度。

從程式碼中可以看出,變數 i 的值從 1 開始取,每迴圈一次就乘以 2。當大於 n 時,迴圈結束。還記得我們高中學過的等比數列嗎?實際上,變數 i 的取值就是一個等比數列。如果我把它一個一個列出來,就應該是這個樣子的:

所以,我們只要知道 x x x 值是多少,就知道這行程式碼執行的次數了。通過 2 x = n 2x=n 2x=n 求解 x x x 這個問題我們想高中應該就學過了,我就不多說了。 x = l o g 2 n x=log_2n x=log2n,所以,這段程式碼的時間複雜度就是 O ( l o g 2 n ) O(log_2n) O(log2n)

現在,我把程式碼稍微改下,你再看看,這段程式碼的時間複雜度是多少?

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

根據我剛剛講的思路,很簡單就能看出來,這段程式碼的時間複雜度為 O ( l o g 3 n ) O(log_3n)

O(log3n)

實際上,不管是以 2 為底、以 3 為底,還是以 10 為底,我們可以把所有對數階的時間複雜度都記為 O ( l o g n ) O(logn) O(logn)。為什麼呢?

我們知道,對數之間是可以互相轉換的, l o g 3 n log_3n log3n 就等於 l o g 3 2 ∗ l o g 2 n log_32 * log_2n log32log2n,所以 O ( l o g 3 n ) = O ( C ∗ l o g 2 n ) O(log_3n) = O(C * log_2n) O(log3n)=O(Clog2n),其中 C = l o g 3 2 C=log_32 C=log32 是一個常量。基於我們前面的一個理論:在採用大 O 標記複雜度的時候,可以忽略係數,即 O ( C f ( n ) ) = O ( f ( n ) ) O(Cf(n)) = O(f(n)) O(Cf(n))=O(f(n))。所以, O ( l o g 2 n ) O(log_2n) O(log2n) 就等於 O ( l o g 3 n ) O(log_3n) O(log3n)。因此,在對數階時間複雜度的表示方法裡,我們忽略對數的“底”,統一表示為 O(logn)。

如果你理解了我前面講的 O ( l o g n ) O(logn) O(logn),那 O ( n l o g n ) O(nlogn) O(nlogn) 就很容易理解了。還記得我們剛講的乘法法則嗎?如果一段程式碼的時間複雜度是 O ( l o g n ) O(logn) O(logn),我們迴圈執行 n 遍,時間複雜度就是 O ( n l o g n ) O(nlogn) O(nlogn) 了。而且, O ( n l o g n ) O(nlogn) O(nlogn) 也是一種非常常見的演算法時間複雜度。比如,歸併排序、快速排序的時間複雜度都是 O ( n l o g n ) O(nlogn) O(nlogn)

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

我們再來講一種跟前面都不一樣的時間複雜度,程式碼的複雜度由兩個資料的規模來決定。

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;
}

從程式碼中可以看出,m 和 n 是表示兩個資料規模。我們無法事先評估 m 和 n 誰的量級大,所以我們在表示複雜度的時候,就不能簡單地利用加法法則,省略掉其中一個。所以,上面程式碼的時間複雜度就是 O ( m + n ) O(m+n) O(m+n)

針對這種情況,原來的加法法則就不正確了,我們需要將加法規則改為: T 1 ( m ) + T 2 ( n ) = O ( f ( m ) + g ( n ) ) T1(m) + T2(n) = O(f(m) + g(n)) T1(m)+T2(n)=O(f(m)+g(n))。但是乘法法則繼續有效: T 1 ( m ) ∗ T 2 ( n ) = O ( f ( m ) ∗ f ( n ) ) T1(m)*T2(n) = O(f(m) * f(n)) T1(m)T2(n)=O(f(m)f(n))

轉自《資料結構與演算法之美》