原文地址:數據結構學習筆記-時間複雜度算法
在進行算法分析時,語句總的執行次數T(n)是關於問題規模n的函數,進而分析T(n)隨n的變化狀況並肯定T(n)的數量級。算法的時間複雜度,也就是算法的時間量度,記做:T(n) = O(f(n))。它表示隨問題規模n的增大,算法執行時間的增加率和f(n)的增加率相同,稱做算法的漸近時間複雜度,簡稱爲時間複雜度。其中f(n)是問題規模n的某個函數。
這樣用大寫O()來體現算法的時間複雜度的記法,咱們稱之爲大O記法。數據結構
如何分析一個算法的時間複雜度呢?即如何推導大O階呢?咱們能夠參考下面的推導方法。函數
推導大O階: 1. 用常數1取代運行時間中的全部加法常數。 2. 在修改後的運行次數函數中,只保留最高階項。 3. 若是最高階項存在且不是1,則去除與這個項相乘的常數。 獲得的結果就是大O階。
下面讓咱們根據這個推導方法來看幾個例子。學習
int sum = 0,n = 100; /* 執行一次 */ sum = (1 + n) * n/2; /* 執行一次 */ System.out.println(sum); /* 執行一次 */
這段程序的執行次數是f(3)。咱們使用大O階的方法推導一下:code
沒有最高階項,因此這段程序的時間複雜度爲O(1).get
能夠試想一下,若是這段代碼裏的it
sum = (1 + n) * n/2; /* 執行一次 */
一共有10句,那麼時間複雜度是多少呢?table
事實上,不管有多少句該代碼,都不過是3次和屢次的執行差別。像這種執行時間恆定的算法,咱們稱之爲具備O(1)的時間複雜度,又叫常數階。循環
注意:不管這個常數是多少,咱們都記做O(1)。學習筆記
同理,對於單純分支結構(不包含在循環中的if或switch語句)而言,執行的次數都是恆定的,其時間複雜度也是O(1)。
線性階的循環結構會複雜一些。要肯定某個算法的階次,咱們經常須要肯定某個特定語句或某個語句集的運行次數。所以,咱們要分析算法的複雜度,關鍵就是要分析循環結構的運行狀況。
下面這段代碼,它的循環時間複雜度爲O(n),由於循環中的代碼需要執行n次。
for(int i = 0; i < n; i++){ }
int count = 1; while(count < n){ count = count * 2; }
因爲每次count乘以2以後,就離n更近了一些。也就是是,有多少個2相乘後大於n,則會退出循環。由2^x=n獲得x=log2n(以2爲底n的對數)。因此這個循環的時間複雜度爲O(logn)。
下面例子是一個循環嵌套,它的內循環時間複雜度爲O(n)。
int i,j; for(i = 0; i < n; i++){ for(j = 0; j < n; j++){ } }
對於外循環不過是這個時間複雜度爲O(n)的語句循環了n次,因此這段代碼的時間複雜度爲O(n^2)。
若是外循環的次數改成了m,那麼時間複雜度就變爲了O(m*n)。
因此咱們能夠總結出來:循環的時間複雜度等於循環體的複雜度乘以循環運行的次數。
那麼,下面這段代碼的時間複雜度是多少呢?
int i,j; for(i = 0; i < n; i++){ for(j = i; j < n; j++){ } }
咱們能夠推導一下當i = 0 時,內循環執行了n次;當i = 1時,執行了n - 1次,……當 i = n-1時,執行了1次。因此總的執行次數爲:
n + (n-1) + (n-2) + …… + 1 = n(n+1)/2 = n^2/2 + n/2
用推導大O階的方法
最終,這段代碼的時間複雜度就是O(n^2)。
該表列舉了一些常見的時間複雜度
執行次數函數 | 階 | 非正式術語 |
---|---|---|
12 | O(1) | 常數階 |
2n+3 | O(n) | 線性階 |
3n^2+2n+1 | O(n^2) | 平方階 |
5log2n(2爲底n的對數)+20 | O(logn) | 對數階 |
2n+3log2n(2爲底n的對數)+19 | O(nlgon) | nlogn階 |
6n^3+2n^2+3n+4 | O(n^3) | 立方階 |
2^n | O(2^n) | 指數階 |
經常使用的時間複雜度從小到大依次是:
O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n)