數據結構之算法時間複雜度

想要學會算法時間複雜度,那麼就要先弄清楚幾個概念。

  1. 什麼是算法時間複雜度?
  2. 它有什麼用呢?
  3. 寫法記作 T(n)=O(f(n))
  • T(n):語句執行的總次數關於n的函數
  • n:問題規模
  • f(n):問題規模n的某個函數
  • 用O()來體現算法時間複雜度的記法

時間複雜度的定義是:如果一個問題的規模是n,解決這一問題所需算法所需要的時間是n的一個函數T(n),則T(n)稱爲這一算法的時間複雜度。
所謂算法時間複雜度就是一句話:算法中基本操作的執行次數。既然是T(n)的函數,隨着模塊n的增大,算法執行的時間的增長率和T(n)的增長率成正比,所以T(n)越小,算法的時間複雜度越低,算法的效率越高。

那麼它有什麼用呢?剛纔也說了,可以通過f(n)的函數關係來評估算法的效率問題,說白了就是通過時間複雜度來看算法的好壞

值得注意的是:有的算法中基本操作執行次數不僅僅跟初始輸入的數據規模(n)有關,還和數據本身有關。例如一些排序算法,同樣n個待處理數據,數據初始有序性不同,基本操作執行次數也會不同。如果算法中有特殊要求,一般依照使得基本操作執行次數最多的輸入來計算時間複雜度,即將最壞的情況最爲算法時間複雜度的度量。

常見的是按複雜度的大小



                                                                        常見的時間複雜度

有的人會對log2 n與log n做對比,不理解這裏爲什麼不一樣,其實這兩個是一樣的也就是圖中第2個和第4個都是可以替換以2爲底的對數形式。

對數時間
主條目:對數時間
若算法的T(n) = O(log n),則稱其具有對數時間。由於計算機使用二進制的記數系統,對數常常以2爲底(即log2 n,有時寫作lg n)。然而,由對數的換底公式,loga n和logb n只有一個常數因子不同,這個因子在大O記法中被丟棄。因此記作O(log n),而不論對數的底是多少,是對數時間算法的標準記法。————維基

如何計算或者推導時間複雜度呢##

我們來分析一下常規做法:

  1. 確定算法中的基本操作以及問題的規模。
  2. 根據基本操作執行情況計算出規模n的函數f(n),並確定時間複雜度爲T(n)=O( f(n)中增長最快的項/此項的係數 ).

那麼是什麼意思呢?記住這個利器,這三句話即可。

  1. 用常數1替換所有加法常數。
  2. 在修改後的運行次數的函數中,只保留最高階項。
  3. 如果最高階項不是1(例如O(1)),則把該項的係數除掉,得到O

開始實戰##

這不是演戲,這不是演習,實戰之後就可以完全掌握概念了。

第一類

看下面一對代碼,進行分析:

int i=0,n=100;        /*執行了一次*/
i=n/2+n;              /*執行了一次*/
printf("i=%d",i);     /*執行了一次*/ 

那麼不難分析出這段代碼一共執行了3次,那麼時間複雜度就是O(3),對吧?是不是很簡單,如果真的是這樣,那就錯了,看我們的利器第一句,它是f(n)=3,所以應該把3改爲1,即O(1)。那麼看下面這個:

int i=0,n=100;        /*執行了一次*/
i=n/2+n;              /*執行了一次*/
printf("i=%d",i);     /*執行了一次*/ 
printf("i=%d",i);     /*執行了一次*/ 
printf("i=%d",i);     /*執行了一次*/ 
printf("i=%d",i);     /*執行了一次*/ 
printf("i=%d",i);     /*執行了一次*/ 

這段代碼一共執行了7次,那麼時間複雜度爲多少呢,經過上面的坑,這個應該沒問題了,對,f(n)=7,把7改爲1,即O(1)。那麼我們可以得知,這種代碼是具有恆定的執行時間的,也就是代碼不會因爲問題規模n的變化而發生變化,所以我們都記爲O(1).

第二類

void fun(int n)
{
    int i=1,j=100;
    while(i<n)
    {
        ++j;
        i+=2;
    }
}

這個顯然n確定以後,循環的開始結束都是與i有關的,且每次自增2,假設m次後結束循環,那麼i應該等於1+2m,那麼就有n=1+2m,因爲我們要是執行次數,也就是解得m=(n-1)/2,此時我們可以看出n/2增長的是最快的項,根據我們的法寶,我們需要把前面的係數除掉即可得到O,即(n/2)/(1/2)=n,得O(n).

有的爲了更嚴謹的推導,會對上面的式子進行修改,即1+2m+K=n ,K爲一個常數,因爲循環的結束的時候往往i是稍稍大於n的,所以用一個K來修正這個式子,m=(n-1-K)/2,當然因爲K爲常數,所以不會影響最終結果,畢竟有一個增長更快的傢伙把它的影響幹掉了。

做到這,是不是感覺很簡單了呢?那麼我們趁熱打鐵進行下一個。

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

推導時間複雜度,最重要的就是要分析算法的執行次數。那麼這段程序怎麼分析呢?試着自己分析一下,再來看吧。好啦,i起始值爲1,每次都乘2,也就意味着每次都會距離n近一些,那麼什麼時候超過n而終止循環呢?很簡單就是i22222...*2>n,那麼假設k次之後大於n,就有2^k=n,得出k=logn(上面說了還有些log2 n,都是一樣的,以後都寫最簡形。)
馬上就要成功了,主要是練就分析算法和推導的思路。再來一個:

int i,j,x=0;
for(i=0;i<n;i++)
{
    for(j=0;j<n;j++)
        x++;
}

這段代碼不用多想就知道,外循環執行n次,內循環也是執行n,則O(n^2).那麼這段呢?

int i,j,x=0;
for(i=0;i<n;i++)
{
    for(j=i;j<n;j++)
        x++;
}

由於當i=0,時內循環執行了n次,i=1時,執行了n-1次,...i=n-1時,執行了1次,那麼總次數爲 n+(n-1)+(n-2)+..+1=n(n+1)/2,那麼就是n2/2,即O(n2).

到這裏基礎的就結束了,我想大家也應該能看懂了吧,當然還有一些比較複雜的算法,大家可以去自行試試,對於該文章不懂得可以在文章下面留言,看到了我會回覆的。

最後給大家做個練習吧。

i++;
function(n)  /* 方法function(n)爲時間複雜度O(n)*/
int k,m;
while(k<n)
{
    function(n);
    k++;
}
for(k=0;k<n;k++)
{
    for(m=k;m<n;m++)
    {
        /*時間複雜度爲O(1)的序列*/
    }
}

小試牛刀,檢驗成果吧,大家聯繫完這個,函數調用的時間複雜度也被你征服了,對於這個題可以在評論區留下你的答案,並和大家分享吧!

發佈了44 篇原創文章 · 獲贊 73 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章