本篇文章收錄於專輯:http://dwz.win/HjK
前言
你好,我是彤哥,一個每天爬二十六層樓還不忘讀原始碼的硬核男人。
上一節,我們從事後統計法過渡到漸近分析法,詳細講解了如何進行演算法的複雜度分析。
但是,如果遵循嚴格的漸近分析法,需要掌握大量數學知識,這無疑給我們評估演算法的優劣帶來了很大的挑戰。
那麼,有沒有更好地評估演算法的方法呢?
答案是必然的,本節,我們就從最壞、平均、最好三種情況來分析分析複雜度。
案例
為了便於講解,我寫了一個小例子:
public class LinearSearch {
public static void main(String[] args) {
int[] array = new int[]{1, 8, 9, 3, 5, 6, 10, 13};
int index = search(array, 10);
System.out.println("index=" + index);
}
private static int search(int[] array, int value) {
for (int i = 0; i < array.length; i++) {
if (array[i] == value) {
return i;
}
}
return -1;
}
}
這個例子使用線性搜尋從一個陣列中查詢一個元素,這個元素有可能存在,也有可能不存在於陣列中。
最壞情況
在最壞情況下,要查詢的元素不存在於陣列中,此時,它的時間複雜度是多少呢?
很簡單,必然需要遍歷完所有元素才會發現要查詢的元素不存在於陣列中。
所以,最壞情況下,使用線性查詢的時間複雜度為O(n)。
平均情況
在平均情況下,我們要照顧到每一個元素,此時,它的時間複雜度如何計算呢?
在上一節,我們已經講過計算方式了,不過,這裡考慮到有元素不存在於陣列中,所以,是(n+1)種可能:
1*1/(n+1) + 2*/(n+1) + ... + n*1/(n+1) + (n+1)/(n+1) = 1/(n+1) * (n+1)(n+2)/2 = (n+2)/2
所以,在平均情況下,忽略掉常數項,使用線性查詢的時間複雜度也是O(n)。
為什麼要忽略掉常數項?
當n趨向於無窮大的時候,常數項的意義不是很大,所以,可以忽略,比如(n+2)/2=n/2 + 1,n本身已經趨向於無窮大,加不加1有什麼意義呢,n的倍數是2還是1/2並不會有明顯的差別。
同樣地,低階項一般也會抹掉,比如2n^2 + 3n + 1,當n趨向於無窮大的時候,n^2的值是遠遠大於3n的,所以,不需要保留3n。
所以,計算複雜度時通常都會把常數項和低階項抹掉,只保留高階項。
最好情況
最好情況是什麼呢?
如果我們要查詢的元素正好是陣列的第一個元素,查詢一次就找到了,這無疑是最好的情況。
所以,在最好情況下,使用線性查詢的時間複雜度是O(1)。
小結
通過上面的分析,可以看到,最壞情況和最好情況是比較好評估的,而平均情況則比較難以計算。
但是,最好情況又不能代表大多數樣本,且平均情況與最壞情況在省略常數項的情況下往往是比較接近的。
所以,通常,我們使用最壞情況來評估演算法的時間複雜度,這也是比較簡單的一種評估方法,且往往也是比較準確的。
後記
本節,我們從最壞、平均、最好三種情況分析了線性查詢的時間複雜度,經過詳細地分析,我們得出結論,通常使用最壞情況來評估演算法的時間複雜度。
請注意,我們這裡使用了“通常”,說明有些情況是不能使用最壞情況來評估演算法的時間複雜度的。
那麼,你知道什麼情況下不能使用最壞情況來評估演算法的時間複雜度嗎?
下一節,我們接著聊。
關注公眾號“彤哥讀原始碼”,解鎖更多原始碼、基礎、架構知識!