解惑3:時間頻度,算法時間復雜度


一、概述

先放百科上的說法:

算法的時間復雜度(Time complexity)是一個函數,它定性描述該算法的運行時間。這是一個代表算法輸入值的字符串的長度的函數。

時間復雜度常用大O符號表述,不包括這個函數的低階項和首項系數。使用這種方式時,時間復雜度可被稱為是漸近的,亦即考察輸入值大小趨近無窮時的情況。

例如,如果一個算法對於任何大小為 n (必須比 n0 大)的輸入,它至多需要 5n3 + 3n 的時間運行完畢,那么它的漸近時間復雜度是 O(n3).

二、時間頻度

要理解時間復雜度,需要先理解時間頻度,而時間頻度簡單的說,就是算法中語句的執行次數

舉個例子:

要計算1+2+...+100,現在有兩種算法

public int fun1(int n){
    int total;
    for(int i = 0; i <= n; i++){
        total+=i;
    }
    return total;
}

public int fun2(int n){
    int total = (1 + n)*n/2;
    return total;
}

我們可以看見,對於fun1()這個方法,不管n多大,永遠需要執行n+1次,也就是說他的時間頻度是T(n)=n+1,

而對與fun2()來說,不管n多大都只需要執行1次,所以他的時間頻度T(n)=1。

當n趨向無窮大時,有三個忽略

1.忽略常數項

比如T(n)=2n+1,當n趨向無窮大時,可以忽略常數項1;

參見下圖:

  • 2n+20 和 2n 隨着n 變大,執行曲線無限接近, 20可以忽略
  • 3n+10 和 3n 隨着n 變大,執行曲線無限接近, 10可以忽略

2.忽略低次項

比如T(n)=2n+3n^8,當n趨向無窮大時,可以忽略低次項及其系數2n;

參見下圖:

  • 2n^2+3n+10 和 2n^2 隨着n 變大, 執行曲線無限接近, 可以忽略 3n+10
  • n^2+5n+20 和 n^2 隨着n 變大,執行曲線無限接近, 可以忽略 5n+20

3.忽略系數

比如T(n)=2n^8,當n趨向無窮大時,可以忽略系數2。

參見下圖:

  • 隨着n值變大,5n^2+7n 和 3n^2 + 2n ,執行曲線重合, 說明 這種情況下, 5和3可以忽略。
  • 而n^3+5n 和 6n^3+4n ,執行曲線分離,說明多少次方式關鍵

三、時間復雜度

我們現在理解了時間頻度的T(n)的含義,假設當有一個輔助函數f(n),使得當n趨近無窮大時,T(n)/f(n)的極限值為不等於0的常數,就叫f(n)為T(n)的同量級函數,記作T(n)=O(f(n)),

稱O(f(n))為算法的時間漸進復雜度,也就是時間復雜度

又根據時間頻度T(n)的“三個忽略”原則,我們可以知道時間復雜度是這樣得到的:

  1. 忽略所有常數
  2. 只保留函數中的最高階項
  3. 去掉最高階項的系數

舉個例子:

某算法T(n)=2n^3+4n-5,按步驟走:

  1. T(n)=2n^3+4n
  2. T(n)=2n^3
  3. T(n)=n^3

即可得該算法時間復雜度為O(n^3)

四、常見時間復雜度

這里按復雜度從低到高列舉常見的時間復雜度:

  1. 常數階O(1)

    // 無論代碼執行了多少行,只要是沒有循環等復雜結構,那這個代碼的時間復雜度就都是O(1) 。
    public void fun(int n){
        n+=1;
    }
    
  2. 對數階O(log2n)

    // 根據公式有 n = 2^x,也就是 x = log2n,x即為循環代碼執行次數,所以時間復雜度為O(log2n)
    public void fun(int n){
        int i = 1;
        while(i < n){
            i = i *2
        }
    }
    
  3. 線性階O(n)

    // 一般來說,只要代碼里只有一個循環結構,即輸入規模和執行次數呈線性相關,那這個代碼的時間復雜度就都是O(n) 。
    public void fun(int n){
        for(int i = 0; i < n; i++){
            n+=i;
        }
    }
    
  4. 線性對數階O(nlogn)

    // 可以簡單理解為對數階的程序被放入了循環結構中,也就是n*O(logn),下面的代碼的復雜度就是O(nlog2n)
    public void fun(int n){
        int j = 1;
        for(int i = 0; i < n; i++){
            while(i < n){
                j = j *2
            }
        }
    }
    
  5. 平方階O(n²),立方階O(n3),K次方階O(nk)

    // 平方階可以簡單理解為線性階中嵌套一個線性階,也就是O(logn)*O(logn),下面的代碼復雜度就是O(n^2)
    // 立方階同理,就是三個線性階的嵌套,K次方階同理
    public void fun(int n){
        for(int i = 0; i < n; i++){
            for(int j = 0; j < n; i++){
    			i=i+j;
            } 
        }
    }
    

五、復雜度的四個概念

  1. 最壞情況時間復雜度:代碼在最理想情況下執行的時間復雜度。
  2. 最好情況時間復雜度:代碼在最壞情況下執行的時間復雜度。
  3. 平均時間復雜度:用代碼在所有情況下執行的次數的加權平均值表示
  4. 均攤時間復雜度:在代碼執行的所有復雜度情況中絕大部分是低級別的復雜度,個別情況是高級別復雜度且發生具有時序關系時,可以將個別高級別復雜度均攤到低級別復雜度上。基本上均攤結果就等於低級別復雜度。

舉個例子:

長度為n的數組查找一個給定元素k

public void fun(int[] arr,int k){
    for(int i = 0; i < arr.length; i++){
        if(arr[i] == k){
            //找到了
        }
    }
}

上面這個方法,最好的情況下元素k就在數組第一位,復雜度為O(1),但是最壞的情況下,元素k在數組最后一位,復雜度為O(n)。

同一段代碼在不同情況下時間復雜度會出現量級差異,為了更全面,更准確的描述代碼的時間復雜度,我們引入這4個概念,當然,在大多數時候我們是不用特意區分這四種情況的。

六、總結

總結一下如何快速判斷程序的時間復雜度:

  • 只關注循環最多的那部分代碼
  • 總復雜度等於量級最大的那段代碼的復雜度
  • 嵌套代碼的復雜度等於嵌套內外代碼復雜度的乘積


免責聲明!

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



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