1. 程式人生 > 其它 >【資料結構&演算法】02-複雜度分析之執行效率和資源消耗

【資料結構&演算法】02-複雜度分析之執行效率和資源消耗

目錄

前言

本筆記主要記錄如何分析、統計演算法的執行效率和資源消耗

必須學會分析複雜度分析。

李柱明部落格:https://www.cnblogs.com/lizhuming/p/15487271.html

複雜度

複雜度分為:

  1. 時間複雜度。關聯到執行效率

    • 時間複雜度的全稱是 漸進時間複雜度
      表示演算法的執行時間與資料規模之間的增長關係
  2. 空間複雜度。關聯到資源消耗

    • 空間複雜度全稱就是漸進空間複雜度表示演算法的儲存空間與資料規模之間的增長關係

分析方法

大 O 複雜度表示法

先說結論:

  • 大 O 複雜度表示方法只是表示一種變化趨勢。
  • 忽略掉公式中的常量、低階、係數,只需要記錄一個最大階的量級就可以了
  • 多、加法和乘法規則

例子-評估累加和的各種演算法執行效率

演算法 1(for 迴圈):
int cal(int n) 
{
  int sum = 0;
  int i = 1;
  for (; i <= n; ++i)
  {
    sum = sum + i;
  }
  return sum;
}
  • 從 CPU 角度看:

    • 重複類似的操作:讀資料-運算-寫資料。
    • 假設每行程式碼執行事件都為 unit_time。(粗略估計)
    • 程式碼中執行的時間為:T(n) = (2+3n)*unit_time
  • 結論:所有程式碼的執行時間 T(n) 與每行程式碼的執行次數成正比。

演算法 2(巢狀 for 迴圈):
int cal(int n) 
{
  int sum = 0;
  int i = 1;
  int j = 1;
  for (; i <= n; ++i) 
  {
    j = 1;
    for (; j <= n; ++j) 
    {
      sum = sum +  i * j;
    }
  }
}
  • 程式碼中執行時間為:T(n) = (3+3n+3n²)*unit_time=3(n²+n+1)*unit_time
  • 所有程式碼的執行時間 T(n) 與每行程式碼執行的次數成正比。

大 O 表示

T(n) = O(f(n))

  • 上面演算法 1 中的大 O 表示法為:T(n) = O(2+3n)

  • 上面演算法 2 中的大 O 表示法為:T(n) = O(3(n²+n+1))

  • 大 O 表示法不是表示程式碼真正的執行時間,而是表示程式碼執行時間隨資料規模增長的變化趨勢

    • 即是時間複雜度

當 n 很大時,公式中的低階常量係數三部分並不左右增長趨勢,所以都可以忽略。

只需要記錄一個最大量級就可以了,如果用大 O 表示法表示剛講的那兩段程式碼的時間複雜度,可記為:

  • T(n) = O(n)
  • T(n) = O(n²)

時間複雜度分析

當了解了大 O 表示法後,就可以用來分析時間複雜度了。

三個實用的方法:

  1. 只關注迴圈執行次數最多的一段程式碼。
  2. 加法法則:總複雜度等於量級最大的那段程式碼的複雜度。(
  3. 乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積。(巢狀:

關注執行最多的一段程式碼

以上面演算法 1 為例:

  • 前面兩行程式碼為常量級別,忽略。

  • 3n 中的係數也可忽略。

  • 結論:時間複雜度為 O(n)

演算法 2 的時間複雜度就是 O(n²)

加法規則

加法法則:總複雜度等於量級最大的那段程式碼的複雜度

  • 公式:T(n)=T1(n)+T2(n)=max(O(f(n)), O(g(n))) =O(max(f(n), g(n)))

若上面演算法 1 和演算法 2 出現在同一個程式碼段中是,其時間複雜度之和為 O(n)+O(n²)

總的時間複雜度就是取最大量級: O(n²)

乘法規則

乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積

  • 公式: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))

例子:

  • 時間複雜度:T(n) = T1(n) * T2(n) = O(n*n) = O(n²)
int func(int n) 
{
 int sum = 0;
 int i = 1;
 for (; i < n; ++i) 
 {
   sum = sum + i;
 } 
 return sum;
}

int cal(int n)
{
  int ret = 0; 
  int i = 1;
  for (; i < n; ++i) 
  {
    ret = ret + func(i);
  } 
} 

常見時間複雜度

常見時間複雜度量級如圖:

這些複雜度量級可分為:

  • 多項式量級:

    • 常量階:O(1)
    • 對數階:O(logn)
    • 線性階:O(n)
    • 線性對數階:O(nlogn)
    • k 次方階:O(nk)注意:這裡的 k 為 k 次方)
  • 非多項式量級

    • O(2n);(注意:這裡的 n 為 n 次方)
    • O(n!)
    • 說明:當資料規模 n 越來越大時,非多項式量級演算法的執行時間會急劇增加,求解問題的執行時間會無限增長。所以,非多項式時間複雜度的演算法其實是非常低效的演算法

常量階 O(1)

O(1) 只是常量級時間複雜度的一種表示方法,並不是指只執行了一行程式碼。

大牛總結:(常量級記作 O(1)

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

對數階 O(logn)、O(nlogn)

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

時間複雜度分析過程:

  • 多:第 4 行程式碼執行次數最多。那就算出第四行執行的次數。

  • 得 x = log2n 即時間複雜度為 O(log2n)。也就是 O(logn)

  • 不管底數為何值,都把這類對數階的時間複雜度記為 O(logn)。理由:

    • log3n = log32 * log2n。對應時間複雜度為:O(log3n) = O(C * log2n)。
    • 按前面學的係數可忽略:O(log3n) = O(log2n)。
    • 既然不同底數都可以轉化,那就直接使用 O(logn) 來標記對數階。

而對於 O(nlogn) 就是一段時間複雜度為 O(logn) 的程式碼段被執行了 n 次。

多引數階 O(m+n)、O(m*n)

分析程式碼的時間複雜度由兩個以上資料的規模來決定。

以下以兩個資料規模決定為基礎。

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;
}
  • 其時間複雜度為:O(m+n)
  • 對於加法規則(變了):T1(m) + T2(n) = O(f(m) + g(n))。
  • 對於乘法規則(不變):T1(m)*T2(n) = O(f(m) * f(n))。

空間複雜度分析

空間複雜度。關聯到資源消耗

  • 空間複雜度全稱就是漸進空間複雜度表示演算法的儲存空間與資料規模之間的增長關係

使用大 O 表示法,和時間複雜度一樣,只是分析的資料規模 n 由時間度量改為空間度量。

小結

複雜度也叫漸進複雜度,包括時間複雜度和空間複雜度,用來分析演算法執行效率與資料規模之間的增長關係。

通常越高階複雜度的演算法,執行效率越低。

常見的複雜度並不多,從低階到高階有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2 )。