概述
執行效率是考量一個算法是否高效的主要指標。然而時間、空間復雜度又是衡量算法執行效率的主要維度
一、事后統計法的局限
以前通過統計、監控實際代碼的運行就能夠獲取算法執行的時間和占用的內存大小。這種事后統計方法雖然能夠評估算法的執行效率,但還是存在諸多缺陷。
-
測試結果非常依賴測試環境
測試環境中硬件配置的不同對測試結果會產生很大的影響。
-
測試結果受數據規模的影響較大
對于同一個算法而言,測試數據量的大小不同,其所得出的性能測試結果也會不同。
二、大 O復雜度表示法
如下一段代碼:
public int sum(int n){
1 int sum = 0;
2 for(int i = 0; i<n ; i++){
3 sum = sum +1;
}
return sum;
}
假設每一段代碼的執行時間單位為T,那么第1行代碼執行時間為T,第3行代碼由于執行了n次,那么執行時間為nT所以這段代碼的總執行時間為(n+1)T,由此可知:所有代碼的執行時間T(n)與每行代碼的執行次數n成正比。
接下來再看一段代碼
public int sum(int n){
1 int sum = 0;
2 for (int i = 0 ; i<= n ; ++i){
3 for (int j = 0 ; j<= n ; ++j){
4 sum = sum + i*j;
}
}
return sum;
}
這里每行代碼的執行時間單位仍舊為T,那么第1行代碼的執行時間為T,第4行代碼由于執行了n2次,所以執行時間為n2T。因此,整段代碼的執行時間為:(n2+1)T 。
將以上規律總結成一個公式,就是我們所知的大O表達式。
T(n) = O ( f (n) )
其中:T(n)代表代碼所執行的時間;n表示數據規模的大小;f(n)表示每行代碼執行的次數總和。而公式中的O則表明了代碼的執行時間T(n)與 f(n) 表達式成正比。
大O復雜度分析表示法,并不具體表示代碼真正的執行時間,而是表示代碼執行時間隨數據規模增長的變化趨勢,一般叫作漸進時間復雜度(asymptotic time complexity),簡稱時間復雜度。同時,當n趨于無限大的時候,我們就可以忽略公式中的低階、常量、與系數三部分。只需記錄一個最大的量級,如 T(n) = O(2n2 + 2n +3) 我們省略掉其中的低階、常量、與系數所得出的最終結果為:T(n) = O(n2)。因此以上兩段代碼的時間復雜度就可以記為 : T(n) = O(n) ;T(n) = O(n2)。
三、時間復雜度分析
-
只關注循環執行次數最多的一段代碼:
關于這一點,之前的代碼示列就是很好的說明,這里不再贅述。
-
加法法則:總復雜度等于量級最大的那段代碼的復雜度
比如如下一段代碼:
public int sum(int n){ int sumOne = 0; for (int i = 0; i <= 1000 ; ++i){ 1 sumOne += i; } int sumTwo = 0; for (int i= 0; i< n ; ++i){ 2 sumTwo += i; } int sumThree = 0; for (int i = 0; i< n ; ++i){ for (int j = 0 ; j < n ;++j){ 3 sumThree = sumThree + i *j; } } return sumOne + sumTwo + sumThree; }
這段代碼共分為三個部分,分別求sumOne、sumTwo、sumThree。我們可以分別解析每一個部分的時間復雜度,然后再從他們之中選取一個量級最大的作為整段代碼的復雜度。
第一段代碼執行了1000次,所以其代表的是一個常量級的執行時間,跟n的規模無關,可以將其忽略掉。
第二段代碼和第三段代碼分別執行了 n 次和 n2次,所以他們的時間復雜度分別為O(n) 和O(n2)。
綜合這三段代碼的時間復雜度,我們取其中最大的量級,那么整段代碼的復雜度就為:O(n2)
因此將上述這個公式抽象一下就可以得出:
如果:T1(n) = O(f(n)),T2(n)=O(g(n));那么 T(n) = T1(n)+T2(n) = max(O(f(n)),O(g(n)))=O(max(f(n),g(n)))
-
乘法法則:嵌套代碼的復雜度等于嵌套內外代碼復雜度的乘積
如果T1(n) = O(f(n)),T2(n) = O(g(n));那么 T(n) = T1(n) x T2(n) = O(f(n)) x O(g(n) = O(f(n) x g(n))
public int sum(int n){
int result = 0;
for (int i = 0 ; i < n ;++i){
1 result = result + subSum(n);
}
return result;
}
public int subSum(int n){
int subSum = 0;
for (int j = 0 ; j < n ; ++j){
2 subSum += j;
}
return subSum;
}
其中:第一段,第二段代碼都是執行了n次,時間復雜度都是 T(n) = O(n)。但由于第二段代碼是嵌套在第一段代碼之中執行的,因此整個代碼的時間復雜度應為:T(n) = T1(n) x T2(n) = O(n) x O(n) = O(n2)
四、常見時間復雜度
?
多項式量級 | 非多項式量級 |
---|---|
常量階O(1) | 指數階O(2n) |
對數階O(logn) | 階乘階O(n!) |
線性階O(n) | |
線性對數階O(nlogn) | |
平方階O(n2)、立方階O(n3)、··· 、k次方階O(nk) |
-
常量階 O(1)
一般來說,只要算法中不存在循環語句、遞歸語句、即使有成千上萬行代碼,其時間復雜度仍舊是O(1)
-
對數階O(logn),線性對數階O(nlogn)
int i = 1; while( i <= n){ i = i * 2; }
如上一段代碼中,i 的取值為 20, 21, 22,···· , 2x-1, 2x 成等比隊列。要想知道這段代碼執行了多少次,求出 2x = n 的結果就行。 x = log2n,所以這段代碼的時間復雜度就是 O( log2n),這里忽視掉底數,那么所有的對數階時間復雜度可以表示為O(logn)。
-
O(m+n)、O(m * n)
public int sum(int m , int n){ int sumOne = 0; for (int i = 0 ; i<= m ; ++i){ sumOne += m; } int sumTwo = 0; for (int j = 0 ; j<= n ; ++j){ sumTwo += j; } return sumOne+sumTwo; }
上述代碼中,復雜度取決于兩個數據規模 m 與 n,因此無法事先評估m 與 n 誰的量級更大,因此加法法則在這里不適用。因此上段代碼的時間復雜度即為O(m+n)。但是乘法法則依舊適用:T1(m) x T2(n) = O(f(m) x f(n))。
五、空間復雜度分析
空間復雜度就是漸進空間復雜度(asymptotic space complexity),表示算法的存儲空間與數據規模之間的增長關系。
public void initArray(int n){
1 int[] arr = new int[n];
for (int i = 0 ; i<= n ; ++i){
arr[i] = i * i;
}
}
如上述代碼所示,我們在第一行代碼中申請了一個大小為n的int類型數組,除此之外其他的代碼都沒有占據更多的空間,因此整段代碼的空間復雜度就是O(n),相對于時間復雜度而言,空間復雜度的分析要更為簡單。