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


 

原文鏈接 
算法的時間復雜度定義為:

在進行算法分析時,語句總的執行次數T(n)是關於問題規模n的函數,進而分析T(n)隨n的變化情況並確定T(n)的數量級。算法的時間復雜度,也就是算法的時間量度,記作:T(n}=0(f(n))。它表示隨問題規模n的增大,算法執行時間的埔長率和 f(n)的埔長率相同,稱作算法的漸近時間復雜度,簡稱為時間復雜度。其中f( n)是問題規橫n的某個函數。

根據定義,求解算法的時間復雜度的具體步驟是:

  • 找出算法中的基本語句

      算法中執行次數最多的那條語句就是基本語句,通常是最內層循環的循環體。

  • 計算基本語句的執行次數的數量級

      只需計算基本語句執行次數的數量級,這就意味着只要保證基本語句執行次數的函數中的最高次冪正確即可,可以忽略所有低次冪和最高次冪的系數。這樣能夠簡化算法分析,並且使注意力集中在最重要的一點上:增長率。

  • 用大Ο記號表示算法的時間性能

      將基本語句執行次數的數量級放入大Ο記號中。

如何推導大o階呢?我們給出了下面 的推導方法:

1.用常數1取代運行時間中的所有加法常數。

2.在修改后的運行次數函數中,只保留最髙階項。

3.如果最高階項存在且不是1,則去除與這個項相乘的常數。

簡單的說,就是保留求出次數的最高次冪,並且把系數去掉。 如T(n)=2n^2+n+1 =O(n^2)

例如:

#include "stdio.h" int main() { int i, j, x = 0, sum = 0, n = 100; /* 執行1次 */ for( i = 1; i <= n; i++) /* 執行n+1次 */ { sum = sum + i; /* 執行n次 */ for( j = 1; j <= n; j++) /* 執行n*(n+1)次 */ { x++; /* 執行n*n次 */ sum = sum + x; /* 執行n*n次 */ } } printf("%d", sum); /* 執行1次 */ }

 

按照上面推導“大O階”的步驟,我們來看 
第一步:“用常數 1 取代運行時間中的所有加法常數”, 
則上面的算式變為:執行總次數 =3n^2 + 3n + 1 
(直接相加的話,應該是T(n) = 1 + n+1 + n + n*(n+1) + n*n + n*n + 1 = 3n^2 + 3n + 3。現在用常數 1 取代運行時間中的所有加法常數,就是把T(n) = 3n^2 + 3n + 3中的最后一個3改為1. 就得到了 T(n) = 3n^2 + 3n + 1)

第二步:“在修改后的運行次數函數中,只保留最高階項”。 
這里的最高階是 n 的二次方,所以算式變為:執行總次數 = 3n^2

第三步:“如果最高階項存在且不是 1 ,則去除與這個項相乘的常數”。 
這里 n 的二次方不是 1 所以要去除這個項的相乘常數,算式變為:執行總次數 = n^2

因此最后我們得到上面那段代碼的算法時間復雜度表示為: O( n^2 )

下面我把常見的算法時間復雜度以及他們在效率上的高低順序記錄在這里,使大家對算法的效率有個直觀的認識。

O(1) 常數階 < O(logn) 對數階 < O(n) 線性階 < O(nlogn) < O(n^2) 平方階 < O(n^3) < { O(2^n) < O(n!) < O(n^n) }
  • 1

最后三項用大括號把他們括起來是想要告訴大家,如果日后大家設計的算法推導出的“大O階”是大括號中的這幾位,那么趁早放棄這個算法,在去研究新的算法出來吧。因為大括號中的這幾位即便是在 n 的規模比較小的情況下仍然要耗費大量的時間,算法的時間復雜度大的離譜,基本上就是“不可用狀態”。

一 計算 1 + 2 + 3 + 4 + …… + 100。 
常規算法,代碼如下:

#include "stdio.h" int main() { int i, sum = 0, n = 100; /* 執行1次 */ for( i = 1; i <= n; i++) /* 執行 n+1 次 */ { sum = sum + i; /* 執行n次 */ //printf("%d \n", sum); } printf("%d", sum); /* 執行1次 */ }

 

代碼附加的注釋可以看到所有代碼都執行了多少次。那么這寫代碼語句執行次數的總和就可以理解為是該算法計算出結果所需要的時間。該算法所用的時間(算法語句執行的總次數)為: 1 + ( n + 1 ) + n + 1 = 2n + 3

而當 n 不斷增大,比如我們這次所要計算的不是 1 + 2 + 3 + 4 + …… + 100 = ? 而是 1 + 2 + 3 + 4 + …… + n = ?其中 n 是一個十分大的數字,那么由此可見,上述算法的執行總次數(所需時間)會隨着 n 的增大而增加,但是在 for 循環以外的語句並不受 n 的規模影響(永遠都只執行一次)。所以我們可以將上述算法的執行總次數簡單的記做: 2n 或者簡記 n

這樣我們就得到了我們設計的算法的時間復雜度,我們把它記作: O(n)

二 求兩個n階方陣C=A*B的乘積其算法如下:

    void MatrixMultiply(int A[n][n],int B [n][n],int C[n][n]) { (1) for(int i=0; i <n; i++) //n+1 { (2) for (j=0;j < n; j++) //n*(n+1) { (3) C[i][j]=0; //n^2 (4) for (k=0; k<n; k++) //n^2*(n+1) { (5) C[i][j]=C[i][j]+A[i][k]*B[k][j]; //n^3 } } } } 

 

則該算法所有語句的頻度之和為:

T(n) = 2n^3+3n^2+2n+1; 利用大O表示法,該算法的時間復雜度為O(n^3)。

三 分析下列時間復雜度


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

 

設for循環語句執行次數為T(n),則 i = 2T(n) + 1 <= n - 1, 即T(n) <= n/2 - 1 = O(n)

四 分析下列時間復雜度


void test_2(int b[], int n) { int i, j, k; for (i=0; i<n-1; i++) { k = i; for (j=i+1; j<n; j++) { if (b[k] > b[j]) { k = j; } } x = b[i]; b[i] = b[k]; b[k] = x; } }

 

其中,算法的基本運算語句是 
if (b[k] > b[j]) 

k = j; 

其執行次數T(n)為: 
這里寫圖片描述

五 分析下列時間復雜度

void test_3(int n) { int i = 0, s = 0; while (s<n) { i++; s = s + i; } }

 

其中,算法的基本運算語句即while循環內部分, 
設while循環語句執行次數為T(n),則 
這里寫圖片描述

六 Hanoi(遞歸算法)時間復雜度分析

void hanoi(int n, char a, char b, char c) { if (n==1) { printf("move %d disk from %c to %c \n", n, a, c);  //執行一次 } else { hanoi(n-1, a, c, b);  //遞歸n-1次 printf("move %d disk from %c to %c \n", n, a, c);  //執行一次 hanoi(n-1, b, a, c);  //遞歸n-1次 } }

 

對於遞歸函數的分析,跟設計遞歸函數一樣,要先考慮基情況(比如hanoi中n==1時候),這樣把一個大問題划分為多個子問題的求解。 
故此上述算法的時間復雜度的遞歸關系如下: 
這里寫圖片描述

這里寫圖片描述 
這里寫圖片描述 
這里寫圖片描述 
這里寫圖片描述 
這里寫圖片描述

常用排序算法時間復雜度

這里寫圖片描述

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM