在很多情況下,僅僅通過算法的描述(如加法和乘法)就可以分辨出算法的性能是線性級還是平方級的。例如,平方級的主要特征是嵌入的循環結構。但是,這樣的直接分析對某些算法卻無法使用。例2-5給出了GCD算法,該算法是由歐幾裡德設計,用于計算兩個整數的最大公約數。
例2-5:歐幾裡得GCD 算法
public static void gcd (int a[], int b[], int gcd[]) {
if (isZero (a)) { assign (gcd, a); return; }
if (isZero (b)) { assign (gcd, b); return; }
a = copy (a); // 確定a和b都沒有被修改
b = copy (b);
while (!isZero (b)) {
if (compareTo (a, b) > 0) {
} else{
}
// a的值就是原先a和b的最大公約數
assign (gcd, a);
這個算法不斷地比較a和b,然後用大數減去小數直至得到0為止。其中的輔助函數(isZero、assign、compareTo和subtract)都可以在代碼庫中找到。
雖然這個算法可以計算出兩個數的最大公約數,但是對于給定的輸入規模,要經過多少次疊代,這個答案并不明顯。由于每一次循環之後,a或者b都不會變為負數,是以能夠保證的是這個算法一定會結束,但是某些GCD的計算可能會需要較長的時間,例如,該算法計算gcd(1000, 1)時會執行999步!很明顯,這個算法的性能對于輸入資料的敏感程度比乘法和加法算法要大得多。對于相同規模的輸入來說,GCD算法的時間是随着輸入資料的變化而變化的。GCD算法在計算(10k-1, 1) 的最大公約數時性能最差,它需要處理n=10k-1次循環!由于加法和減法在資料規模為n時的時間複雜度均為O(n),是以GCD算法可以被歸類為O(n2)。
例2-6中ModGCD算法的性能要比例2-5的GCD實作好得多,這個算法使用取模計算a除以b的餘數。
例2-6:ModGCD 算法計算最大公約數
public static void modgcd (int a[], int b[], int gcd[]) {
if (isZero(a)) { assign (gcd, a); return; }
if (isZero(b)) { assign (gcd, b); return; }
int quot[] = new int[a.length];
int remainder[] = new int[a.length];
while (!isZero(b)) {
int t[] = copy (b);
divide (a, b, quot, remainder);
assign (b, remainder);
assign (a, t);
// a 的值就是原始的(a,b)的最大公約數
ModGCD能夠更快地得到解,因為它不會在while循環中不斷地從大數中減去小數。這個差異并不僅僅展現在實作細節上,也反映了在如何使用算法解決問題方面的一個根本性改變。
圖2-5和表2-5展示了針對随機産生的142個n位數,求解所有10 011對整數對最大公約數的計算結果。
圖2-5:比較gcd和modgcd算法
對于随機資料,即便ModGCD實作比對應的GCD實作約快3倍,它的性能依舊是平方級,也就是O(n2)。分析ModGCD算法的最壞情況有些難度,研究表明當ModGCD處理兩個連續的Fibonacci數字時性能最差。從圖2-5上也可以推斷出算法是平方級的,因為算法性能在資料規模翻倍後變為原先的4倍。
計算最大公約數還有着更為優秀的算法,不過這些算法主要針對特别大的整數,因而在實際場景中并不實用。