得物技術談談算法入:算法的好壞?複雜度告訴你

什麼是算法

百度百科對算法的定義是 「解題方案的準確而完整的描述,是一系列解決問題的清晰指令,算法表明着用系統的方法描述解決問題的策略機制。」算法

簡單的來說,算法就是解決一個問題所用的方法。好比對於數組排序問題,有多種排序方法能夠達到使元素有序排列的目的,每一種正確的排序方法都能稱之爲一個算法。數組

如何衡量算法的好壞

一個問題既然能夠經過多種算法來解決,那麼如何衡量哪一個算法更好呢?顯然一個算法執行時間越短,佔用的內存空間越小,那麼它就是更好的算法。一般有兩種方法來比較:事前分析和過後統計。過後統計就是指先編寫好算法,並運行監控其指標,該方法的問題在於受運行環境、電腦硬件干擾,會掩蓋算法自己的優劣。所以科學的方法是經過數學分析的方法來評估算法的好壞。函數

前面說到算法執行時間越短,佔用內存空間越小,算法越好。對應的,咱們經常用時間複雜度來表明算法執行時間,空間複雜度表明算法佔用的內存空間。.net

時間複雜度

通常來說,一個算法花費的時間與其中執行的語句的次數成正比,一個算法中的語句執行次數稱爲時間頻度,用 T(n) 來表示。好比有 func 函數以下:code

function func(n) {
    for(let i = 0; i < n; i ++) {       // 執行n+1次
        console.log('Hello World!')     // 執行n次
    }
    return                              // 執行1次
}

對於 func 來說,該算法運行時,共執行了 2n+2 次運算,那麼該算法的時間頻度 T(n) = 2n + 2, n 爲算法輸入的大小,即輸入的數據規模,當 n 不斷變化時,T(n) 也會隨之變化。blog

假設有某個輔助函數 f(n), 當 n 趨於無窮大時,總有 T(n) <= C f(n) (C 爲常數) 成立,即 T(n) 的上界是 C f(n),記做 T(n) = O(f(n)) ,稱 O(f(n)) 爲算法的漸進時間複雜度,O 表示正比例關係,f(n) 表示代碼執行次數之和, 這種表示方法叫作 「 大O符號表示法 」。排序

因此對於函數 func 來說,有 T(n) = O(2n+2),能夠簡化爲 T(n) = O(n)。爲何能夠簡化呢,是由於大O表示法只是用來表明執行時間的增加變化趨勢,當 n 無窮大時,2n+2 中的常量就沒有意義了,同時由於符號O中隱藏着常數C,因此 n 的係數2能夠省略到C中。f(n) 中 n 通常不加係數。同理,咱們能夠獲得 O(2n²+n+1) = O(2n²) = O(n²)。若是把 T(n) 表示成一棵樹,O(f(n)) 表示的就是樹幹,其餘的細枝末節對於複雜度的影響遠小於樹幹。內存

通常來說,衡量一個算法的複雜度,咱們每每只須要判斷循環結構裏基本操做發生了多少次,就能表明該算法的時間複雜度。get

常見的時間複雜度數學

  • 常數階 O(1)

加減乘除(eg. i++, i--)、數組尋址(eg. array[10])、賦值操做(eg. a=1)等都屬於常數級的複雜度,簡言之若是沒有循環等複雜結構,不隨輸入數量級 n 變化的操做,該代碼的時間複雜度都屬於常數級複雜度,無論這種代碼有多少行。

  • 對數階 O(logn)
function func(n) {
    let i = 0;
    while(i < n) {
        i *= 2;
    }
}

對如上 func 函數來說,在 while 循環中,假設當循環到第 m 次時, 有 i = n ,即 2 的 m 次方等於 n,可知 m = logn (每每將以2爲底省略),即 while 循環內的操做運行了 logn 次,該算法的時間複雜度即爲logn 。

  • 線性階 O(n)
function func(n) {
    sum = 0;
    for(let i = 0; i < n; i++) {
        sum += i;
    }
    return sum; 
}

如上 func 函數,for 循環中的原子操縱執行了 n 次,該算法的時間複雜度即爲 O(n)。

  • 線性對數階 O(nlogn)
function func(n) {
    for(let m = 1; m < n; m++)
    {
        let i = 1;
        while(i < n)
        {
            i = i * 2;
        }
    }
}

如上函數,有雙層循環結構,顯然根據以前的時間複雜度能夠推斷出來該算法的時間複雜度爲 O(nlogn) ,固然上面的函數是爲了湊這個複雜度而湊的,並無實際的意義。

  • 平方階 O(n²)
function func(n) {
    let sum = 0;
    for(let i = 0; i < n; i ++) {
        for(let j = 0; j < n; j ++) {
            sum += i * j;
        }
    }
    return sun;
}

如上函數,有雙層循環結構,第5行代碼,共循環運行了 n² 次,因此該算法的時間複雜度爲 O(n²)。

  • 立方階 O(n³),K次方階O(n^k)

和平方階同理,有多層循環結構,該循環結構中的原子操做循環的次數。

常見的算法時間複雜度由小到大依次爲:Ο(1)<Ο(logn)<Ο(n)<Ο(nlogn)<Ο(n²)<Ο(n³)<…<Ο(2^n)<Ο(n!)。

空間複雜度

空間複雜度是對一個算法在運行過程當中輔助變量臨時佔用存儲空間大小的一個量度。空間複雜度和時間複雜度同理,也採用大O表示法,比較經常使用的有:O(1)、O(n)。

  • O(1)

算法執行時所須要的臨時空間不隨着數據規模 n 的大小變化,則算法的空間複雜度爲 O(1),好比對於數組求和,其中 sum 和 i 都是臨時變量,所須要的空間不隨array長度的大小而變化,因此該函數的空間複雜度爲 O(1)。

function func(array) {
    let sum = 0;
    for(let i = 0; i < array.length; i++) {
        sum += array[i];
    }
    return sum;
}
  • O(n)
function func(array) {
    let new_array = [...array];
    return new_array;
}

如上函數是複製了輸入 array 數組,返回複製的數組,在函數中,建立了 array.length 長度的新數組 new_array,該算法的空間複雜度和原數組 array 的長度成正比,故該算法的時間複雜度爲 O(n),n 表明數組 array 的長度。

至此咱們已經瞭解了常見的時間複雜度和空間複雜度,時間複雜度的分析須要根據具體的算法來分析,有了這個基礎,咱們就能針對實際的算法進行分析啦!

參考

https://www.zhihu.com/question/21387264

https://zhuanlan.zhihu.com/p/50479555

https://www.jianshu.com/p/f4cca5ce055a

http://www.javashuo.com/article/p-awbjfjnb-hb.html

文|hustlmn

關注得物技術,攜手走向技術的雲端

相關文章
相關標籤/搜索