重拾算法之復雜度分析(大O表示法)


 

重拾算法之復雜度分析(大O表示法)

在論壇里經常會看到一句話:學不會算法就去做網頁開發。當然,從某種層面來看,前端對算法的要求確實不高,畢竟想寫一個級聯選擇器會找到一大把的組件庫。但是呢,算法又是一個優秀的工程師必須具備的的基礎內功。程序員之間流傳這么一句話:“一流程序員靠數學,二流靠算法,三流靠邏輯,四流靠SDK,五流靠Google和StackOverFlow,六流靠百度和CSDN。低端的看高端的就是黑魔法!”。對於面試一些大公司而言,尤其是國外的互聯網企業,算法也是繞不過的一道坎。大學時買過一本很厚的算法書《算法導論》,翻開書當時就被里面的高等數學嚇退了,后來也再也沒看過。對於算法初學者來講,看《算法導論》、《計算機程序設計藝術》這樣的算法聖經不是很好的途徑,所以我決定從一些簡單的書籍像《算法圖解》、《數學之美》以及王爭(前google工程師)的付費專欄《數據結構與算法之美》開始算法學習,代碼主要以javascript為主,可能會穿插C語言,如果有精力刷題,可能會加入一些我認為有趣的leetcode算法題。學習筆記也會在博客一直更新,希望能做一個相對完整的更新。

復雜度分析,會貫穿整個算法學習過程中。王爭說,掌握了復雜度分析,就掌握了算法學習的一半。因此,今天我們來聊復雜度分析——大O復雜度表示法。

 

一個簡單的公式

引用算法圖解一個有趣的例子:Bob要寫一個算法計算月球登陸前執行幫助計算着陸點的算法,他用兩種算法:簡單查找跟二分查找。假設查找一個元素要1ms,查找100的元素簡單查找要100ms,二分查找只需要檢查7個元素( log2(7)),大概7ms。而如果要查找10億個元素,用二分查找就是30ms左右,簡單查找卻要10億毫秒。為什么會這樣呢?因為兩種算法運行時間的增速不同。在高中數學中學概率問題都是基於大量數據統計,同樣,估算算法的執行效率,也是基於對運行時間與數據的漸進式增長的粗略分析,我們的大O表示法該登場了!

假設每個語句執行時間為t,則以下代碼:

function sum (){
  let sum = 0;     /*執行時間t*/
  let i = 0;       /*執行時間t*/
  let j = 0;       /*執行時間t*/

  for(; i < n; i++){     /*執行時間n*t */
    j = 1;                        /*執行時間n*t*/
    for(; j < n; j++){   /*執行時間n的平方*t*/
      sum = sum + i * j;          /*執行時間n的平方*/
    }
  }
}

整段代碼整的執行時間為: T(n) = (2n²+2n+3)*t ,所有代碼的執行時間T(n)與每行代碼的執行次數成正相關!大O表示法計算的並不是代碼的正式運行時間,而是代碼執行時間隨數據規模增長的變化趨勢,時間復雜度

 

時間復雜度分析

1. 只關注循環執行次數最多的一段代碼

在分析代碼復雜度的時候,只關注循環執行次數最多的那一段代碼就可以,如下代碼:

 function cal(n{
   let sum = 0;
   for (let i = 0; i <= n; i++) {
     sum = sum + i;
   }
   return sum;
 }

上述代碼我們只需要關注for循環代碼執行次數n,所以中的時間復雜度為O(n)。

2. 加法法則:總復雜度等於量級最大的那段代碼的復雜度

我們回到這篇文章的第一段代碼,該代碼中的執行時間為 T(n) = (2n²+2n+3)*t ,所以量級最大的那段代碼運行時間為n的平方,則這段代碼的時間復雜度為:O(n²).

3. 乘法法則:嵌套代碼的復雜度等於嵌套內外代碼復雜度的乘積

function cal(n{
   let ret = 0
   for (let i + 0; i < n; i++) {
     ret = ret + f(i);
   } 
 } 

function f(n{
  int sum = 0;
  for (let i = 0; i < n; i++) {
    sum = sum + i;
  } 
  return sum;
}

以上代碼在cal函數中嵌套f函數,cal函數不看f,時間復雜度為O(n),f函數的時間復雜度為O(n),整個cal函數的時間復雜度為O(n²)。

常見的時間復雜度量級

 

1. O(1)

 let i = 8;        /*  時間復雜度: O(1)  */
 let j = 6;        /*  時間復雜度: O(1)  */
 let sum = i + j;  /*  時間復雜度: O(1)  */

2. O(logn)、O(nlogn)

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

上述代碼,當i = 1時,進入while后,i 賦值為2,下一次賦值為4,依次指數增長,那么他在while中執行的次數為log2(n),忽略系數,則時間復雜度為O(logn)。在排序中如歸並排序,快速排序的時間復雜度為O(nlogn)。

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

上述代碼的復雜度為 m + n。

4. O(n2)----選擇排序

5. O(n!)
著名的旅行商問題,有一位旅行商,要去5個城市,要求找出路程總和最少的路線,我們只能把所有可能的路線可能5!= 120中方法算一遍來找出,這是計算機領域無最優解的問題。

 

空間復雜度

空間復雜度全稱就是漸進空間復雜度,表示算法的存儲空間與數據規模之間的增長關系。此處不做詳細說明。

 

不同的大O運行時間

1. 最好情況時間復雜度與最壞時間復雜度

// n 表示數組 array 的長度
function find(array, n, x{
  let pos = -1;
  for ( let i = 0; i < n; i++) {
    if (array[i] == x) {
       pos = i;
       break;
    }
  }
  return pos;
}

在一個長度為n的數組中找一個數,如果在遍歷第一次就找到,則時間復雜度為O(1),如果整個數組遍歷了一遍,那么時間復雜度為O(n)。
最好情況時間復雜度就是,在最理想的情況下,執行這段代碼的時間。最壞情況時間復雜度就是,在最糟糕的情況下,執行這段代碼的時間。

2. 平均情況時間復雜度

高中時,我們都學過數學期望,平均時間復雜度的實質就是數學期望。
以上代碼中,查找X在數組中的位置有n+1中情況,即在0~n-1或者不在數組中,把查找的每種情況遍歷的元素除以查找次數,就是遍歷元素的平均值。

 

忽略系數,則時間復雜度為O(n)。但是,在概率統計中,查找元素時,要么可以查找到,要么差找不到,我們假設他們的概率各位一半,那么計算如下:

 

最終得到的時間復雜度為O(n)。

3. 均攤時間復雜度

 /* array 表示一個長度為 n 的數組
  代碼中的 array.length 就等於 n */
 let array = new Array[n];

 function insert(val) {
    if (count == array.length) {
       let sum = 0;
       for (let i = 1; i < array.length; i++) {
          sum = sum + array[i];
       }
       array[0] = sum;
       count = 1;
    }

    array[count] = val;
    count++;
 }

以上代碼有兩種情況:

  1. 當count不等於數組長度時,每次調用函數的時候,時間復雜度為O(1),這種情況出現的次數為n次;

  2. 當count等於數組長度時,調用函數時,走進for循環,此時時間復雜度為:O(n)

  3. 平均時間復雜度計算:

     

該例子較上面的例子的特殊之處在於,函數在大多數情況下的時間復雜度為O(1),而當最壞時間復雜度的時候出現的幾率很小,這時候我們需要引入一種分析的方法叫:攤還分析法,通過該分析法得到的時間復雜度為均攤時間復雜度。

在上面的例子里,每一次O(n)的插入都會有n-1次時間復雜度為O(1)的插入,我們吧耗時多的這次的操作均攤到n-1次耗時少的上面,實際的均攤復雜度就為O(1)。
均攤復雜度的出現情況很少,可以把它認為是一種特殊的平均時間復雜度。它的應用場景都是在一系列連續操縱中,大部分情況復雜度低,個別復雜度高,這時候就可以平攤下來。而且,一般情況下,在能夠應用均攤時間復雜度分析的場合,一般均攤時間復雜度就等於最好情況時間復雜度。


參考文檔:
1: 王爭(前google工程師)專欄 ----《數據結構與算法之美》
2: 《算法圖解》------大O表示法部分

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM