250 likes | 467 Views
第 1 章 基本數學背景與 C 程式背景 簡介. 1.1 漸近式 O(f(n)),Ω(f(n)) 與 θ(f(n)). 粗略估計程式執行時所佔用的時間與記憶體空間大小的方式 , 其定義如下 :. 設 函數 f, g 為定義於自然數 的實值函數 . f, g: . 定義 1: g(n) = O( f ( n) ) iff there are two positive constants c and n o such that
E N D
第 1 章 基本數學背景與 C 程式背景 簡介 1.1 漸近式 O(f(n)),Ω(f(n)) 與 θ(f(n)) 粗略估計程式執行時所佔用的時間與記憶體空間大小的方式, 其定義如下: 設函數 f, g 為定義於自然數 的實值函數. f, g: . 定義 1:g(n) = O( f(n) )iff there are two positiveconstants c and n o such that |g(n) | c | f(n) | for all n n o 我們稱函數 f(n) 為函數g(n) 的一個上界, 並可讀成函數g(n) 為 big-o of 函數 f(n) .
例 1: 2000 n 2 +10000 = O( n 2 ). 證明 : 取 c = 2001, N=101, 則當 n N 時, c n 2 = 2001 n 2=2000 n 2 + n 2 2000 n 2 + 10000 0. 例 2: 1000 n log n + 10000 n + 50 = O( n 2 ). 證明 : 取 c = 1001, N=10001, 則當 n N 時, c n 2 = 1001 n 2=1000 n 2 + n 2 =1000 n 2 + n n 1000 n 2 + 10001 n > 1000 n log n + 10000 n + 50 0.
定理 1 :設函數fn = a m nm ... a 1 n a 0,a m > 0, 為一 m 階多項式函數, 則 f(n = O( nm ). 證明 : 令 c = (m+1)max{| a m|, | a m-1 |, …, | a 0|} 取 N = 1. 則, 當n N 時, c nm | a m| nm | am-1 | nm-1 … | a 0 | | a m nma m-1 nm-1 … a0| = |f(n)|
註 1:O(1) 為一常數函數, 即 f(n) = c, n . 註 2:O(1)< O(log n) < O(n)< O(n log n) < O(n2 ) < O(n3) < O(2n ) 註 3:我們亦可將 O(f(n)) 視為一集合, 其定義為 O(f(n)) = {g(n) | c 0 and n o 0 |g(n)| c |f(n)| n n o } 註 4:因此, 註 3 可改成 O(1) O(log n) O(n) O(n log n) O( n2 ) O( n3 )O( 2n )
定義 2:g(n) = ( f(n) )iff there are two positiveconstants c and n o such that |g(n) | c | f(n) | for all n n o 我們稱函數 f(n) 為函數g(n) 的一個下界, 並可讀成函數g(n) 為 big- omega of 函數 f(n) . 例 3: 2000 n 2 +10000 = ( n 2 ). 證明 : 取 c = 1, N= 0, 則當 n N 時, 2000 n 2 + 10000 n 2 =c n 2 0.
例 4: 2000 n 2 +10000 = ( n log n ). 證明 : 取 c = 1, N= 1, 則當 n N 時, 2000 n 2 + 10000 n 2 n log nc n log n 0.
定義 3:g(n) = ( f(n) )iff there are three positive constants c 1 , c 2 and n o such that c 1| f(n) | |g(n) | c 2| f(n) | for all n n o 我們稱函數 f(n) 與函數g(n) 為對等. 註 5: g(n) = ( f(n) )即 g(n) = O( f(n) )且 g(n) = ( f(n) ). 例 5: 2000 n 2 +10000 = ( n 2 ). 證明 : 由例 1 與 例 3, 可得 2000 n 2 +10000 = ( n 2 ).
1.2 數學遞迴關係與遞迴函數 數學遞迴關係及遞迴函數與 程式的遞迴函數有密切關係, 三者皆可用來表示同一件事, 只是所用的語言不同, 但是, 方式基本上是相似的. 經常用來說明數學遞迴關係的例子是 Fibonacci sequence. 一個 Fibonacci sequence(費氏數列) 是 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, …. 也就是說, 下一個費氏數是前兩個之和.
利用遞迴關係來表示該數列, 其關係式如下: 令 a n表第 n 個費氏數. 則 a 0 = 0, a 1 = 1, a n =a n 2 + a n 1, n 2. 以數學遞迴函數 f( n ) 表第 n 個費氏數. 則 f( 0 ) = 0, f( 1 ) = 1, f( n ) = f(n 2) + f(n 1), n 2.
欲以遞迴函數 f( n )求第4 個費氏數, 即求f(4), 其求法如下: f(4) = f(2) + f(3) = [f(0) + f(1)] + f(3) = [0 + f(1)] + f(3) = [0 + 1] + f(3) = 1 + f(3) = 1 + {f(1) + f(2)} = 1 + {1 + f(2)} = 1 + {1 + [f(0) + f(1)]} = 1 + {1 + [0 + f(1)]} = 1 + {1 + [0 + 1]} = 1 + {1 + 1} = 1 + 2 = 3
f(4) f(3) f(2) f(0) f(1) f(1) f(2) f(0) f(1) 0 1 1 0 1 我們來看 f(4) 的解析樹如下: 此樹共有 9 個內點, 即執行函數 f(n) 共 9 次.
以t(n) 表示函數 f(n)執行的次數, 則 t(0) = 1, t(1) = 1, t(n) = t(n 2) + t(n 1) + 1, n 2. 因此, t(n) 的前 12 項為 1, 1, 3, 5, 9, 15, 25, 41, 67, 109, 177, 287, …. 利用 generating function 或其他技巧, 我們可以得到 f(n)={ [(1+5)/2] n [(1 5)/2] n } / 5., n = 0, 1, 2, ….
1.3 C 程式迴圈與遞迴函數 本節將說明如何以 C 程式利用迴圈與遞迴函數來求第 n 個費氏數. 一般而言, 迴圈程式比遞迴函數較不易寫, 也較難懂. 但執行速度較快. 如果了解了數學上的遞迴關係與遞迴函數, C 程式的遞迴函數是相當容易寫 也較易懂. 但執行速度較慢. 其慢的基本原因是函數的呼叫, 牽扯到暫存器資料的儲存與復原. 返回地址的儲存與執行的復原, 等等額外工作. 有時也會遇到重複計算而浪費時間.
利用迴圈來計算第 n 個費氏數, 其程式如下: int f(int n){ int a0, a1, a2, i; if( n == 0 ) a2 = 0; else if( n == 1 ) a2 = 1; else { a0 = 0; a1 = 1; for( i=2; i<=n; i++) { a2 = a0 + a1; a0 = a1; a1 = a2;}} return a2;}
利用遞迴來計算第 n 個費氏數, 其程式如下: int f(int n){ if( n == 0 ) return 0; else if( n == 1 ) return 1; else return f(n-2)+f(n-1);}
已知第46 個費氏數是 1,836,311,903. 如果資料型態 int 為4 個 bytes, 則該數是我們所能求的最大費氏數. 如果是利用迴圈來計算第 46 個費氏數, 則迴圈共執行 45 次, 其執行所須時間, 遠少於一秒鐘. 但是, 如果是利用遞迴函數的方式來計算第 46 個費氏數, 則函數共呼叫了t(46) 次, 遠超過 1,836,311,903 次, 所需時間夠我們喝杯茶水.
我們再以例子來比較一下 C 程式的迴圈與遞迴程序. 考慮如何將 n 個數1, 2, …, n 依其排列順序列印出來. 如果 n = 3, 其排列順序列印出來的結果如下. 1 2 3 1 3 2 2 1 3 2 3 1 3 1 2 3 2 1
其排列方式是: 依序固定前面 k 個數後, 將其餘 n k 個數按相同方式依其排列順序一一列印. void permute(int n,int k,int a[MAXSIZE]){ int i, int b[MAXSIZE]; if (k == n) printOut(a, n); else { for(i=k; i<=n; i++){ swap(a[k], a[i]); copy(b, a, n); permute(n, k+1, b);}}}
以迴圈按 n 個數的排列順序方式來列印, 其程式如下: #include <stdio.h> #define MAXSIZE 30 void main( ) { int a[MAXSIZE], n, i, s; int b[MAXSIZE], m, j, t; int flag, temp; printf("Give me the size: "); scanf("%d", &n); for(i=1;i<=n;i++) a[i]=i; for(j=1;j<=n;j++) b[j]=0;
while(1) { // get a new permutation // printOut(a, n); for(i=1;i<=n;i++) printf("%4d,", a[i]); printf("\n"); // Test for continuing the next step flag = 1; for(i=1; i<=n; i++) { if( (n-i+1) > a[i] ) { flag = 0; break; } //continue for next step } if(flag) break; // end of the loop
//find next permutation b[1] = a[n]; m = 1; for(i=n-1; i>=1; i--) { flag = 0; for(j=1; j<=m; j++) { if(b[j]>a[i]) { flag = 1;// end of the inner and outer loop //swap a[i] and b[j] temp = a[i]; a[i] = b[j]; b[j] = temp;
t=1; for(s=i+1; s<=n; s++) a[s] = b[t++]; break; }// if (b[j]>a[i]) }// for (j=1; j<=m; j++) if(flag) break; m++; b[m] = a[i]; } } }
以例來說明該程式是如何運作, 當n = 8, 而目前正列印出 1, 2, 3, 4, 8, 7, 6, 5 下一個將列印出的是 1, 2, 3, 5, 4, 6, 7, 8 我們採用的策略是利用兩個陣列a 與 b, 其內容變化如下:
陣列a 陣列b 1, 2, 3, 4, 8, 7, 6 6 > 5 5 1, 2, 3, 4, 8, 7 7 > 5,6 5, 6 1, 2, 3, 4, 8 8 > 5,6,7 5, 6, 7 1, 2, 3, 4 4 < 5 5, 6, 7, 8 現在因4 < 5, 所以將 4 與 5 對調, 再將陣列 b 的內容加入陣列 a, 即為下一個數列如下圖所示. 1, 2, 3, 5, 4, 6, 7, 8