資料結構 之 演算法時間複雜度

Codeagles發表於2018-07-10

版權宣告:本文為 Codeagles 原創文章,可以隨意轉載,但必須在明確位置註明出處!!!

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

  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,那麼就是n^2/2,即O(n^2).

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

###最後給大家做個練習吧。

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)的序列*/
    }
}
複製程式碼

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

相關文章