想要學會算法時間複雜度,那麼就要先弄清楚幾個概念。
- 什麼是算法時間複雜度?
- 它有什麼用呢?
- 寫法記作 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),而不論對數的底是多少,是對數時間算法的標準記法。————維基
如何計算或者推導時間複雜度呢##
我們來分析一下常規做法:
- 確定算法中的基本操作以及問題的規模。
- 根據基本操作執行情況計算出規模n的函數f(n),並確定時間複雜度爲T(n)=O( f(n)中增長最快的項/此項的係數 ).
那麼是什麼意思呢?記住這個利器,這三句話即可。
- 用常數1替換所有加法常數。
- 在修改後的運行次數的函數中,只保留最高階項。
- 如果最高階項不是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)的序列*/
}
}
小試牛刀,檢驗成果吧,大家聯繫完這個,函數調用的時間複雜度也被你征服了,對於這個題可以在評論區留下你的答案,並和大家分享吧!