對於一個初學者來說,作者的Solutions Manual把太多的細節留給了讀者,這里盡自己的努力給出部分習題的詳解:
不當之處,歡迎指正。
1、 按增長率排列下列函數:N,√2,N1.5,N2,NlogN, NloglogN,Nlog2N,Nlog(N2),2/N,2N,2N/2,37,N2logN,N3。指出哪些函數以相同的增長率增長。
答:排列如下2/N < 37 < √2 < N < NloglogN < NlogN < Nlog(N2) < Nlog2N < N1.5 < N2 < N2logN < N3 < 2N/2 < 2N。
其中,NlogN 與 Nlog(N2) 的增長率相同,均為O(NlogN)。
補充說明:a) 其中 Nlog2N 與 N1.5, N3 與 2N/2大小關系的判定,可以連續使用洛必達法則(N->∞時,兩個函數比值的極限,等於它們分別求導后比值的極限)。當然,更簡單的是兩邊直接求平方。
b) 同時注意一個常用法則:對任意常數k,logkN = O(N)。這表明對數增長得非常緩慢。習題3中我們會證明它的一個更嚴格的形式。
2、 函數NlogN 與 N1+ε/√logN (ε>0) 哪個增長得更快?
分析:我們首先考慮的可能是利用洛必達法則,但是對於第二個函數其上下兩部分皆含有變量,難以求導(當然也不是不行,就是麻煩些,如果你願意設y = N1+ε/√logN ,然后對兩邊取對數的話)。這里我們將利用反證法:
要證NlogN < N1+ε/√logN ,即證 logN < Nε/√logN。既然用反證法,那么我們假設 Nε/√logN < logN。兩邊取對數有 ε/√logN logN < loglogN。即 ε√logN < loglogN。設t = logN,則 ε√t < logt <=> ε2t < log2t,這顯然與連續1中b)矛盾,因此假設 Nε/√logN < logN 不成立。因此函數 N1+ε/√logN增長得更快。
3、 證明對任意常數k,logkN = o(N)。
解:要證明命題成立,只需證limn->∞(logkN / N) = 0即可。證明如下:
首先,如果k1 < k2,顯然有 logk1N = o(logk2N) 。且k = 0時,logkn = 1。應用洛必達法則,limn->∞(logiN / N) = limn->∞(ilogi-1N / Nln2) = limn->∞(logi-1N / N) 。(說明:這里舍棄常數是因為我們假設函數最終的比值為0,否則不可簡單的丟棄常數。當然如果比值不為零的話,我們需要返回到這里另行處理,其實證明過程也是不斷嘗試、調整的,此路不通,另行它法嘛:)就是說,通過不斷地規約,最終極限的比值等於零,命題得證。
4、 求兩個函數 f(N) 和 g(N),使得 f(N) ≠ O(g(N)) 且 g(N) ≠ O(f(N))。
一個顯然的例子是函數 f(N) = sinN,g(N) = cosN。
5、 假設需要生成前N個自然數的一個隨機置換。例如,{4,3,1,5,2} 和 {3,1,4,2,5} 就是合法的置換,但 {5,4,1,2,1} 卻不是,因為數1出現兩次而數3卻沒有出現。這個程序常常用於模擬一些算法。我們假設存在一個隨機數生成器 RandInt(i, j),它以相同的概率生成 i 和 j 之間的一個整數。下面是三個算法:
1) 如下填入從 A[0] 到 A[N-1] 的數組 A:為了填入 A[i],生成隨機數直到它不同於已經生成的 A[0],A[1],... ,A[i-1],再將其填入 A[i]。
2) 同算法1),但是要保存一個附加的數組,稱之為 Used(用過的)數組。當一個隨機數 Ran 最初被放入數組 A 的時候,置 Used[Ran] = 1。這就是說,當用一個隨機數填入 A[i] 時,可以用一步來測試是否該隨機數已經被使用,而不是像的一個算法那樣(可能)進行 i 步測試。
3) 填寫該數組使得 A[i] = i + 1。然后:
for(i = 1; i < N; i++) Swap(&A[i], &A[RandInt(0, i)]);
對每一個算法給出你能夠得到的盡可能准確的期望的運行時間分析(用大O)。
解:分析,對於1),容易寫出如下算法:
for(i = 0; i < N; i++){
while(1){
A[i] = RandInt(1, N);
for(j = 0; j < i; j++)
if(A[j] == A[i])
break;
if(j == i)
break;
}
}
調用一次隨機數字生成函數與前面已經生成的隨機數(存放在A數組中的)不同的概率為 (N-i) / N,那么理論上經過 N / (N-i) 次隨機數的生成我們可以確定其與已生成的概率為 1。因此該算法的期望運行時間為
,
。
當然,也可以對分子放大的同時對分母縮小,不過這樣求得的時間界限為O(N2),顯然不如上面的做法精確。這里需要注意的一點是調和級數的前N項和,它是發散的,在計算機科學中的使用頻率要遠比在在數學等其它科目中使用得多。下面給出調和和:
其近似誤差r = 0.5772156649,這個值稱為歐拉常數(Euler’s constant)。
對於2),容易寫出如下算法:
for(i = 0; i < N; i++){
while(1){
A[i] = RandInt(1, N);
if(Used[i] == 0){
Used[i] = 1;
break;
}
}
}
同1)的分析,其運行時間界限顯然為O(NlogN)。
對於3),運行時間為O(N),不消多說。
6、 記錄一個稱為Horner法則的算法,該算法用於計算 F(X) = Σi=0~nAiXi的值。
Poly = 0;
for(i = N; i >= 0; i--)
Poly = X * Ploy + A[i];
7、 給出一個有效的算法來確定在整數 A1 < A2 < A3 < ... < AN 的數組中是否存在整數 i ,使得 Ai = i。你的算法的運行時間是多少?
分析:類似二分查找,直接上代碼:
int(int[] a, int N){
int low = 0, high = N - 1, middle;
while(low <= high){
middle = ((high-low) >> 1) + low;
if(a[middle]) < middle + 1)
low = middle + 1; // 搜索右空間
else if(a[middle] > middle + 1)
high = middle - 1; // 搜索左空間
else
return middle;
}
return -1;
}
易知該算法的運行時間為O(logN)。
8、 如果7題中的語句 low = middle + 1 更該為 low = middle ,那么這個程序還能正確運行嗎?
答:不能,設 low = n,high = n + 1,則 middle = n,程序陷入死循環。
9、 a.編寫一個程序來確定正整數N是否是素數,你的程序在最壞的情形下的運行時間是多少(用N表示)?(你應該能夠寫出O(√N)的算法程序)。
b.令B等於N的二進制表示法中的位數。B的值是多少?
c.你的程序在最壞情形下的運行時間是什么(用B表示)?
d.比較確定一個20(二進制)位的數是否是素數和確定一個40(二進制)位的數是否是素數的運行時間。
e.用 N 還是 B 來給出運行時間更合理,為什么?
解:對於a,由於 √N * √N = N,因此分解 N 時必有一個整數小於 √N。高效的算法思路是:首先,測試N是否能被2整除,不能的話測試N是否能被3,5,7,...,√N整除。編碼如下(是素數返回1,不是返回0):
int IsPrime(int N){
int i;
if(N == 1)
return 0;
if(N % 2 == 0)
return 0;
for(i = 3; i <= int(sqrt(w) + 0.5); i += 2)
if(N % i == 0)
return 0;
return 1;
}
對於b,顯然有,B = O(logN)。
對於c,由於B = O(logN),則2B = O(N),即2B/2 = O(√N),所以用B表示的最壞情況下的運行時間是:O(2B/2)
對於d,后者的運行時間是前者運行時間的平方,由c中的解答易知。
對於e,Wiss說:B is the better measure because it more accurately represents the size of the input.
All Rights Reserved.
Author:海峰:)
Copyright © xp_jiang.
轉載請標明出處:http://www.cnblogs.com/xpjiang/p/4143743.html
參考資料:Data Structures and Algorithm Analysis in C(second edition) Solutions Manual_Mark Allen Weiss_Florida International University.
