卷演算法——時間複雜度

悲劇不上演發表於2021-11-10
[TOC]

學習如何來分析一個演算法的複雜度。

為什麼需要演算法複雜度

為什麼需要引入一個時間複雜度的概念,假設我們直接在在伺服器上允許我們一個程式碼段通過執行時間來給出複雜度,這種方式成為事後統計法。這種方式存在什麼缺陷呢?

  • 受物理機影響的因素較大。如機器上某一時刻資源利用率低,就會導致你的統計存在問題
  • 受資料影響較大。如排序演算法,不同的資料順序都會影響排序演算法。

因此需要引入一個公式來表達資料量與演算法之間的關係。

複雜度的表示方法

常用的是大O複雜度表示法。表示演算法的執行效率。

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

  • T(n) :表示程式碼的執行時間。
  • n:表示資料執行的大小。
  • f(n):表示每行程式碼執行的次數總和。

時間複雜度

規則

表示程式碼執行時間與資料大小直接的關係。常用的規則:

  1. 只關注迴圈次數最多的一次的程式碼

    大O這種複雜度表示方法只是表示一種變化趨勢。我們通常會忽略掉公式中的常量、低階、係數,只需要記錄一個最大階的量級就可以了。

    for($i=0;$i<1000;$i++) {
        // do something
    }
    
    for($j=0;$i<2*$n;$j++) {
        //  do something
    }
    
    // 時間複雜度:O(n)
  1. 加法法則:總複雜度等於量級最大的那段程式碼的複雜度

    for($i=0;$i<$n;$i++) {
        // do something
    }
    
    for($j=0;i<$n*$n;$j++) {
        //  do something
    }
    
    // 時間複雜度:O(n^2)
  1. 乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積

    for($i=0;$i<$n;$i++) {
        for($j=0;$i<$n;$j++) {
            // todo
        }
    }
    
    // 時間複雜度:O(n^2)

常見的時間複雜度

按數量級異常遞增

複雜度公式 描述
O(1) 常量階
O(log n) 對數階
O(n) 線性階
O(n *log n ) 線性對數階
O(n^2), O(n^K) K次方階
O(2^n) 指數階O
O(n!) 階乘
  1. O(1):

    表示常量級,即程式碼的執行時間與資料量沒有關係。

    $a = 3;
    $b = 4;
    $sum = $a + $b;
  1. O(log n):

    如下程式碼,$i 即從1,2,4,8,16... 是一個等比數列,執行次數就等於 以2為底N的對數,即O(log n)。

    不管下面程式碼乘以2還是乘以3,時間複雜度不變。因為對數之間是可以轉化的。log3n就等於log32 * log2n。因此我們忽略底數

    $i = 1;
    while(i<n) {
     $i = $i *2;
    }
  1. O(nlog n)

    著名的快速排序,歸併排序都是該複雜度。

  2. O(m+n), O(m*n)

    複雜度是有兩個引數決定,我們無法預估這兩個引數的大小

    function sumAdd($m, $n) 
    {
      for($i=0;$i<$m;$i++) {
         $m += $i
      }
    
      for($j=0;$j<$n;$j++) {
         $n += $i
      }
    
      return $m+$n
    }

分類

分類為:最好時間複雜度,最壞時間複雜度,平均時間複雜度,均攤時間複雜度。對於下面的程式碼時間複雜度是多少呢?

function FindIndex(array $array, $value)
{
    $index = -1;
    for($i=0;$i<len($array);$i++) {
        if ($array[$i] == $value) {
            $index = $i;
            break;
        }
    }

    return $index;
}
  1. 最好時間複雜度
    在最理想的情況下,執行這段程式碼的時間複雜度。如上面程式碼最好的時間複雜度就是O(1)。

  2. 最壞時間複雜度

    在最壞的情況下,執行這段程式碼的時間複雜度。則上面的最壞時間複雜度O(n)。

  3. 平均時間複雜度(權平均時間複雜度或者期望時間複雜度)

    概率乘以執行時間相加的結果。下圖為上面程式碼的平均複雜度推導公式

    file

  1. 均攤時間複雜度

    將較高的時間複雜度的那次行為均攤到較低的時間複雜度。

    該複雜度的應用場景較少。即有規律的出現情況複雜度不一樣的操作,我們可以將複雜度高的操作均攤到較低的時間複雜度上。一般均攤時間複雜度等於

    // 遍歷陣列插入該值與隨機數,當最後一次的時候計算陣列長度
    function InsertAndSum(array $array, $value)
    {
       $sum = 0;
       $n = len($array);  
       for($i=0;$i<$n ;$i++) { 
           $array[$i] = $value + mt_rand(10,100);
    
           if ($i == $n-1) {
               // 最後一次
               for($j=0;$j<$n ;$j++) {
                   $sum += $array[$j];
               }
           }
       }
    
       return $sum;
    }
    
    // 1. 迴圈N次中,N-1次時間複雜度都是 O(1), 1次是O(n)
    // 2. 均攤最後一次時間複雜度到前N-1次
    // 3. 得到均攤時間複雜度都是O(1)
    

空間複雜度

漸進式空間複雜度,表示資料與儲存佔用空間的一個關係函式。

function NewArray($n) 
{
    $a = 'cx';
    $b = new SplFixedArray($n);
    for($i=0;$i<$n;$i++) {
       $b[$i]  = $a;
    } 
}

// 這段程式碼的空間複雜度就是O(n), 因為在第4行申請了一個n的記憶體空間

常見的空間複雜度

  • O(1)

    function NewArray($n) 
    {
        $a = 'cx';
        return $a;
    }
  • O(n)

    function NewArray($n) 
    {
        $a = 'cx';
        $b = new SplFixedArray($n);
        for($i=0;$i<$n;$i++) {
           $b[$i]  = $a;
        } 
    }
  • O(n^2)

    function NewArray($n) 
    {
        $a = 'cx';
        $b = new SplFixedArray($n*$n);
        for($i=0;$i<$n*$n;$i++) {
           $b[$i]  = $a;
        } 
    }

複雜度趨勢

file

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章