如何理解算法複雜度

算法

算法的定義是這樣的:解題方案的準確而完善的描述,是一系列解決問題的清晰指令,就是解決一個問題的完整性描述。javascript

如何衡量一個算法的好壞,能夠經過空間複雜度和時間複雜度兩個方面來進行衡量。java

  1. 空間複雜度 評估執行程序所需的存儲空間。能夠估算出程序對計算機內存的使用程度。算法

  2. 時間複雜度 評估執行程序所需的時間。能夠估算出程序對處理器的使用程度。數組

設計算法時,時間複雜度要比空間複雜度更容易出問題,因此通常狀況下咱們只對時間複雜度進行研究。bash

時間複雜度

時間頻度

若是一個算法所花費的時間與算法中代碼語句執行次數成正比,那麼那個算法執行語句越多,它的花費時間也就越多。咱們把一個算法中的語句執行次數稱爲時間頻度。一般用T(n)表示。n用來表示問題的規模。dom

通常狀況下,算法中基本操做重複執行的次數是n的某個函數,用T(n)表示,f(n)用來描述T(n) 函數中增加最快的部分,。記做T(n)=O(f(n)),稱O(f(n)) 爲算法的漸進時間複雜度,簡稱時間複雜度。函數

該表示方法被成爲大O表示法。工具

大O表示法

時間複雜度經常使用大O符號——O(f(n))表述,不包括這個函數的低階項和首項係數。oop

推導大O階有一下三種規則:ui

  1. 用常數1取代運行時間中的全部加法常數
  2. 只保留最高階項
  3. 去除最高階的常數

大O階的推導方法

大O表示法O(f(n))中的f(n)的值能夠爲一、n、logn、n^2 等,因此咱們將O(1)、O(n)、O(logn)、O( n^2 )分別稱爲常數階、線性階、對數階和平方階。下面咱們來看看推導大O階的方法:

常數階

例:段代碼的大O是多少?

let sum = 0, n = 100;
複製代碼

第一條就說明了全部加法常數給他個O(1)便可

線性階

通常含有非嵌套循環涉及線性階,線性階就是隨着問題規模n的擴大,對應計算次數呈直線增加。

let i , n = 100, sum = 0;
    for( i=0; i < n; i++ )
    {
        sum = sum + i;
    }
複製代碼

上面這段代碼,它的循環的時間複雜度爲O(n),由於循環體中的代碼須要執行n次。

平方階

let i, j, n = 100;
    for( i=0; i < n; i++ )
    {
        for( j=0; j < n; j++ )
        {
            console.log('hi')
        }
    }
複製代碼

n等於100,也就是說外層循環每執行一次,內層循環就執行100次,那總共程序想要從這兩個循環出來,須要執行100*100次,也就是n的平方。因此這段代碼的時間複雜度爲O(n^2)。

總結:若是有三個這樣的嵌套循環就是n^3。因此總結得出,循環的時間複雜度等於循環體的複雜度乘以該循環運行的次數。

對數階

let i = 1, n = 100;
    while( i < n )
    {
        i = i * 2;
    }
複製代碼

因爲每次i*2以後,就距離n更近一步,假設有x個2相乘後大於或等於n,則會退出循環。 因而由2^x = n獲得x = log(2)n,因此這個循環的時間複雜度爲O(logn)

舉例:冒泡排序

冒泡排序

假設冒泡排序存在三種狀況,1.數組所有正序排列;2.數組所有反向排列;3.數組混亂排序;

第一種狀況下,數組只需遍歷一遍就能夠完成排序,假設數組長度爲n,須要遍歷n-1次,此時:

T(n) = n-1 = T(O(n));
複製代碼

第二種狀況,數組所有反向排列,那麼就須要一次次遍歷數組直到順序正確,第一次排序須要執行n-1步,第二次排序能夠排除排序正確的數,只須要執行(n-1)-1)次,以此類推,直到最後執行1次完成排序,該過程次數爲:

T(n)=n(n-1)/2 = T(O(n^2);
複製代碼

第三種狀況,數組混亂排序,可能不須要執行到最後便可完成排序,假設到第i次便可完成排序,該過程執行次數:

T(n)=n(n-i)/2 = T(O(n^2);
複製代碼

綜上,冒泡排序的平均時間複雜度爲

O(n^2)
複製代碼

規則解析

推導大O階有一下三種規則:

  1. 用常數1取代運行時間中的全部加法常數
  2. 只保留最高階項
  3. 去除最高階的常數

當n增大到必定程度時,f(n)中最高階的部分佔據了主導地位,低階變量和常數對結果對影響幾乎能夠忽略不計。

工具地址:函數生成器

低階變量影響

低階變量的影響

圖中的兩個函數分別爲

y=3x^2
複製代碼
y=3x^2+2x
複製代碼

隨着X的變大,兩個函數曲線幾乎重合,常數10000的影響微乎其微,可忽略不計。

常量的影響

圖中的兩個函數分別爲

y=2x^2
複製代碼
y=2x^2+10000
複製代碼

隨着X的變大,兩個函數曲線幾乎重合,常數10000的影響微乎其微,可忽略不計。

常數影響

同理,高階變量前的常量也可忽略不計

高階常量忽略不計

綜合

綜上,低階變量和常量對最終結果影響不大

綜合影響

常見時間複雜度的比較

複雜度比較

O(1)<O(logn)<O(n)<O(nlogn)<O(n²)<O(n³)<O(2ⁿ)<O(n!)

大O階的應用

數組去重

方法1:

var arr = [1,2,2,4,3,4,1,3,2,7,5,6,1]
    var newArr = new Set(arr); //n
複製代碼
T(n)=n=T(O(n));
複製代碼

該算法時間複雜度爲

O(n);
複製代碼

方法2:

function fn(arr){
       let newArr = [] // 1
       arr.sort((a,b)=>{
           return a-b
       }) // O(n)=n^2
       
       arr.forEach((val,index)=>{ // n
           if(val != arr[index+1]){ // 1
                newArr.push(val) // 1
           }
       })
       return newArr //1
    }
複製代碼
T(n) =T(O(n^2)) +n+2=T(O(n^2));
複製代碼

該算法時間複雜度爲

O(n^2);
複製代碼

方法3:

for(var i=0;i<arr.length;i++){ //n 
        for(var j=i+1;j<arr.length;j++){ // n*n
             if(arr[i]==arr[j]){ // n*n
                  arr.splice(j,1) // n*n*n
             }
        }
    } 
複製代碼
T(n)=n^3+2n^2+n=T(O(n^3));
複製代碼

該算法時間複雜度爲

O(n^3);
複製代碼

算法對比

O(n)表示了算法的複雜程度,可是並不表明複雜程度越大的算法,消耗時間越長,具體須要根據n的值來判斷。以上面的3種數組去重算法爲例,分析n不一樣的狀況下,不一樣算法的消耗時間。

for(var i = 0,arr =[];i<n;i++){
        arr[arr.length]=parseInt(Math.random()*n);
    }    
    function fn1(arr){
       let newArr = [] // 1
       arr.sort((a,b)=>{
           return a-b
       }) // O(n)=n^2
    
       arr.forEach((val,index)=>{ // n
           if(val != arr[index+1]){ // 1
                newArr.push(val) // 1
           }
       })
       return newArr //1
    }
    function fn2(arr){
        for(var i=0;i<arr.length;i++){ //n 
            for(var j=i+1;j<arr.length;j++){ // n*n
                 if(arr[i]==arr[j]){ // n*n
                      arr.splice(j,1) // n*n*n
                 }
            }
        }
    }
    var newArr = new Set(arr)
    console.time("newArr");
    new Set(arr);
    console.timeEnd("newArr");
    console.time("fn1");
    fn1(arr);
    console.timeEnd("fn1");
    console.time("fn2");
    fn2(arr);
    console.timeEnd("fn2");
複製代碼

n=500

500結果1

500結果2

500結果3

n=1000

1000結果1

1000結果2

1000結果3

n=2000

2000結果1

2000結果2

2000結果3

n=5000

5000結果1

5000結果2

5000結果3

n=10000

10000結果1

10000結果2

10000結果3

綜合以上的對比結果能夠得知,當n<1000時,算法3的時間小於算法2的時間,儘管算法2的時間複雜度小於算法3。

當一個算法當T(n)能夠獲知時,能夠經過對比T(n)來決定使用哪一種算法。

綜合上文大O階表示法的推導規則,當n的數值絕對大時,低階表達式和常數的影響微乎其微,而上述的應用也驗證了這一觀點,當n超過1000時,O(n^3)的算法耗時大大超過O(n^2)的算法。

所以,當算法應用的狀況較爲複雜時,利用時間複雜度——O(n)來判斷是行之有效的方法。時間複雜度能夠是評價一個算法的相對條件,但不是絕對條件。

相關文章
相關標籤/搜索