演算法分析與設計基礎 第二章謎題
習題2.1
4. a. 選擇手套 在一個抽屜裡有22隻手套:5雙紅手套、4雙黃手套和2雙綠手套。你在黑暗中挑選手套,而且只能在選好以後才能檢查它們的顏色。在最優的情況下,你最少選幾隻手套就能找到一雙匹配的手套?在最差的情況下呢?
解答:最優情況:2只;最差情況:12只。
b. 丟失的襪子 假設在洗了5雙各不相同的襪子以後,你發現有兩隻襪子找不到了。當然,你希望留下數量最多的襪子。因此,在最好的情況下,你會留下4雙完整的襪子,而在最壞情況下,只會有3雙完整的襪子。假設10只襪子中,每隻丟失的概率都相同,請找出最佳情況的發生概率和最差情況的發生概率。在平均情況下,你能夠指望留下幾雙襪子呢?
解答:
10. 象棋的發明
a. 根據一個著名的傳說,國際象棋是許多世紀以前由一個印度西北部的賢人沙什發明的。當他把該發明獻給國王的時候,國王很喜歡,就許諾可以給這個發明人任何他想要的獎賞。沙什要求以這種方式給他一些糧食:棋盤的第一個方格內只放1粒麥粒,第二格2粒,第三格4粒,第四格8粒,依次類推,直到64個方格全部放滿。如果計算1粒麥粒需要1秒鐘時間,那麼計算完所有的格子的麥粒需要多長時間?
解答:t=1+2+4+……263=264-1≈1.84*10^19秒
b. 如果每個格子的麥粒數是前一個格子的麥粒數加2,那麼計算完所有格子的麥粒將花費多長時間?
解答:t=1+3+5+……(2*64-1)=64*64=4096秒
習題2.2
11. 更輕或者更重?
你有n>2個外觀相似的硬幣和一個沒有砝碼的天平。其中一枚為假幣,但並不知它比真幣重還是輕。設計一個Θ(1)的演算法來確定假幣比真幣重還是輕。
分析:題目只是要求確定假幣比真幣重還是輕,並不要求找出這枚假幣,n的數量不確定(n>=3),在絕大多數情況下只需在天平上比較兩次即可知道假幣的相對輕重,極端情況也只需三次。
解答:由於n>2,我們分兩種情況解決:
情況一:n是3的整數倍。此時可以將這堆硬幣按數量等分為三堆,設為A、B、C三堆,假幣一定在其中一堆中。第一次在天平上比較A堆和B堆的重量。分為三種子情況:
a. A與B等重,則A、B堆均為真幣,假幣一定在C中。第二次比較A與C,若A比C重,則假幣比真幣輕,否則假幣比真幣重。
b. A比B重,則C堆一定全為真幣,第二次比較A與C,若A與C等重,則假幣在B中且假幣比真幣輕,若A比C重,則假幣比真幣重,若A比C輕,則假幣比真幣輕。
c. A比B輕,同情況b,C堆一定全為真幣,第二次比較A堆與C堆,若A與C等重,則假幣在B中且假幣比真幣重,若A比C重,則假幣比真幣重,若A比C輕,則假幣比真幣輕。
情況二:n不是3的整數倍。此時仍然可以將這堆硬幣按數量等分為三堆A、B、C,多出來的1~2個硬幣先放到一邊。第一次在天平上比較A堆和B堆的重量。也分為三種子情況:
a. A與B等重,則A、B均為真幣。第二次比較A與C,若A比C重,則假幣一定在C中且假幣比真幣輕。若A比C輕,假幣仍然一定在C中且假幣比真幣重。若A與C等重,則假幣只能在放到一邊的多出來的1~2個硬幣中,A、B、C三堆均為真幣。這種極端情況需要第三次用天平比較,將這1~2枚多出來的幣與同等數量的真幣比較,若同等數量的真幣更重,則假幣比真幣輕,否則假幣比真幣重。
b. A比B重,則C堆一定全為真幣,假幣在A或B中。第二次比較A與C,若A與C等重,則假幣在B中且假幣比真幣輕,若A比C重,則假幣比真幣重,若A比C輕,則假幣比真幣輕。
c. A比B輕,同子情況b,C堆一定全為真幣,假幣在A或B中。第二次比較A與C,若A與C等重,則假幣在B中且假幣比真幣重,若A比C重,則假幣比真幣重,若A比C輕,則假幣比真幣輕。
12. 牆上的門 你面前是一堵朝兩個方向無限延伸的牆。牆上有一扇門,但你並不知道門離你有多遠,也不知道門位於哪個方向。你只有走到門面前才能看到它。假設從當前位置到門要走n步,請設計一個演算法,使你最多走O(n)步就能遇到門。
分析:首先,以當前位置為原點,門距離原點有n步(n未知),而且門位於哪個方向也不知道,所以不能只朝一個方向去找,萬一選錯方向就永遠找不到門了。只能像鐘擺一樣以原點為中心來回找。也不能每多走一步就往另一個方向走,因為這樣找到門時已經走了2*(1+2+3+...+n)步,即n*(n+1)步,對應於O(n^2)複雜度,不滿足題目要求。
解答:考慮 i 從0開始,先從原點出發向右走2^i步,返回原點,再向左走2^i步,再返回原點,接著向右走2^(i+1)步,返回原點,再向左走同樣的2^(i+1)步...直到在途中找到門為止。由於門到原點的距離為n步,我們能找到一個整數k,使得2^(k-1) < n <= 2^k. 這樣在找到門之前,你最多走了4*(2^0 + 2^1 + ... + 2^(k-1)) + 3*2^k 步,即4 * (2^k - 1) + 3 * 2^k < 7 * 2^k = 14 * 2^(k - 1) < 14 * n。這樣就保證最多走O(n)步就能遇到門。
習題2.3
10. 心算數 如下圖所示,在一個10*10的表格中,用相同數字在對應的對角線上填滿,心算表格上所有數字之和。
解答:以數字10所在直線為對稱軸,兩邊的數字加起來都是20,把每一個格子看成10,總和為1000.
12. 馮·諾依曼鄰居問題 考慮下列演算法:從一個1*1的方格開始,每次都會在上次圖形的周圍再加上一圈方格,在第n次的時候要生成多少個方格?下圖給出了n=0,1,2時的結果。
分析:n=0, 1個方格;n=1,5個方格;n=2,13個方格;n=3, 25個方格……在生成方格時發現比n-1時多了4條邊,有兩條邊的格子數為n+1,有兩條格子數為n-1,一共是4n。所求方格數為等差數列的和
解答:
13. 頁面編號 假設頁面從1開始連續編號,共有1000頁。計算所有的頁碼中十進位制數字的總個數。
分析:個位數字:1000;十位數字:1000-9=991;百位數字:1000-99=901,千位數字:1
解答:2893
習題2.4
5. 漢諾塔謎題
a. 漢諾塔謎題最早是由一個法國數學家盧卡斯於19世紀90年代提出的。當時的版本是這樣的:當64個圓盤被從梵塔上移走時,世界末日也就來臨了,如果祭司一分鐘移動一個圓盤,請估計一下,移走全部圓盤一共需要多少年?
解答:分鐘=≈3.51*10^13年
b. 在該演算法中,要移動第i大的盤子一共需要移動多少步?
分析:在三個盤子情況下,從大到小的盤子分別移動了1、2、4次,因為每一次移動第i大盤子,都要先將第i+1大的盤子移走,然後移動第i大盤子,再把第i+1大的盤子移回來。所以次數為等比數列。
解答:2^(i-1)
c. 為漢諾塔謎題設計一個非遞迴的演算法,並用你熟悉的語言實現。
非遞迴演算法:定義從小到大的盤子序號分別為1,2,……n。可以用一個1到2n - 1的2進位制序列可以模擬出n個盤子的漢諾塔過程中被移動的盤子的序號序列。即給定一個n,我們通過0到2n - 1序列可以判斷出任意一步應該移動那個盤子。判斷方法:第m步移動的盤子序號是m用二進位制表示的最低位bit為1的位置。
證明: n = 1,顯然成立。假設n = k 成立。n = k + 1時,對應序列1到2k+1 - 1,顯然這個序列關於2k左右對稱。假設我們要把k + 1個盤子從A移動C。那麼2k可以對應著Move(k + 1, A, C)。 1 到 2k - 1 根據假設可以對應Hanoi(A, B, C, k), 定義 void Hanoi(char src, char des, char via, int n)表示把n個盤子從src上藉助via移動到des上。至於2k + 1 到 2k + 1 - 1把最高位的1去掉對應序列變成1到2k - 1,顯然2k + 1 到 2k + 1 - 1和1到2k - 1這兩個序列中的對應元素的最低位bit為1的位置相同。因此2k + 1 到 2k + 1 - 1可以對應Hanoi(B, C, A, k)。所以對n = k + 1也成立。
下面討論第m步應該移動對應的盤子從哪到哪?定義順序為 A->B->C->A, 逆序為C->B->A->C。
性質: 對n個盤子的漢諾塔,任意一個盤子k(k <= n)k在整個漢諾塔的移動過程中要麼一直順序的,要麼一直逆序的。而且如果k在n個盤子移動過程的順序和k - 1(如果k > 1)以及k + 1(如果k < n)的順序是反序。
比如:n = 3,其中1的軌跡A->C->B->A>C逆序,2的軌跡A->B->C順序,3的軌跡A->C逆序
1 A->C
2 A->B
1 C->B
3 A->C
1 B->A
2 B->C
1 A->C
證明:假設n <= k成立。對於n = k + 1,根據遞迴演算法,Hanoi(A,C,B,k + 1) = Hanoi(A, B, C, k) + Move(A, C, k + 1) + Hanoi(B,C,A,k);整個過程中盤子k + 1只移動一次A->C為逆序對應著2k。對於任意盤子m < k + 1,m盤子的移動由兩部分組成一部分是前半部分Hanoi(A, B, C, k)以及後半部分的Hanoi(B, C,A,k)組成。顯然如果m在Hanoi(A, C, B, k)軌跡順序的話,則m在Hanoi(A, B, C, k)以及Hanoi(B, C,A,k)都是逆序。反之亦然。這兩部分銜接起來就會證明m在Hanoi(A,C,B,k)和Hanoi(A,C,B,k + 1)中是反序的。同時有Hanoi塔中最大的盤子永遠是逆序且只移動1步,A->C。
這樣的話:m = k + 1,在Hanoi(A,C,B,k + 1)中是逆序。m = k,由於在Hanoi(A,C,B,k)中是逆序的,所以Hanoi(A,C,B,k + 1)中是順序的。m = k - 1,由於在Hanoi(A,C,B,k - 1)是逆序的,所以Hanoi(A,C,B,k)是順序的,所以Hanoi(A,C,B,k + 1)是逆序的。依次下去……結論得證。
總結:在n個漢諾中n, n - 2,n - 4……是逆序移動,n - 1, n - 3,n - 5……是順序移動。
#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n;
char order[2][256];
char pos[64];
for (int i = 0; i<64; i++)
{
pos[i] = 'A'; //初始的時候,所有的圓盤位置都是 'A';
}
order[0]['A'] = 'B';
order[0]['B'] = 'C';
order[0]['C'] = 'A';
order[1]['A'] = 'C';
order[1]['B'] = 'A';
order[1]['C'] = 'B';
//0是順序 1是逆序
int index[64];
//確定軌跡的順序還是逆序
int i, j, m;
for (i = n; i > 0; i -= 2)
index[i] = 1;
for (i = n - 1; i > 0; i -= 2)
index[i] = 0;
memset(pos, 'A', sizeof(pos));
for (i = 1; i < (1 << n); i++)
{
for (m = 1, j = i; j % 2 == 0; j /= 2, m++); //計算出當前步驟序號的最低的 bit 為 1 的位置。
cout << m << " : " << pos[m] << " --> " << order[index[m]][pos[m]] << endl;
pos[m] = order[index[m]][pos[m]]; //更改當前位置
}
return 0;
}
12. 重溫馮·諾依曼鄰居問題 建立一個遞推關係並求解,以計算n階馮·諾依曼鄰居的細胞數。
解答:如習題2.3.11所述,S(n)=S(n-1)+4n當n>0,S(0)=1,可以直接利用等差數列求和公式得到
13. 烤漢堡 有n個漢堡需要在烤架上烤,但是烤架上一次只能放2個漢堡。每個漢堡都需要兩面烤,不管是烤一個漢堡還是同時烤兩個漢堡,考好一個漢堡的一面用時1分鐘。假設要在最短的時間內完成該任務,考慮下列遞迴演算法。如果n<=2,一個漢堡單獨烤並翻面,兩個漢堡則同時烤並翻面。如果n>2,兩個漢堡同時烤並翻面,然後給餘下n-2個漢堡遞迴地應用同樣的過程。
a. 給出該演算法烤n個漢堡所需要的遞推關係並求解。
解答:遞推關係S(n)=S(n-2)+2當n>2,S(1)=S(2)=2。所以當n為偶數,S(n)=n;當n為奇數,S(n)=n+1;
b. 對於任意n>0個漢堡,該演算法完成任務的時間並不是最少的,為什麼?
解答:因為當n為大於1的奇數時,需要的最少時間可以是n分鐘,比如當n=3時,先烤兩個漢堡1分鐘,然後換掉一個漢堡烤1分鐘,最後再把未烤完的漢堡再烤1分鐘。
c. 給出一個在最少時間內完成烤漢堡任務的正確遞迴演算法。
解答:如前一問所述,當n為奇數且還剩下3個漢堡時,改變烤漢堡的方法,使得最後3個漢堡只需3分鐘即可烤完。遞推關係S(n)=S(n-2)+2當n>3,S(1)=S(2)=2,S(3)=3。
14. 名人問題 n個人中的名人是指這樣一個人:他不認識別人,但是每個人都認識他。任務就是找出這樣一個名人,但只能通過詢問“你認識他/她嗎?”這種問題來完成。設計一個高效演算法,找出該名人或者確定人群中有沒有名人。你的演算法在最壞情況下需要問多少個問題。
解答:通過詢問一個問題,每次減少一個人選。遞迴進行。對最後一人再詢問一或兩個問題確認。
第一步:從人群中選擇兩個人A和B。問A是否認識B。如果A認識B,刪除A。如果A不認識B,刪除B。
第二步:對剩下n-1個人遞迴進行上一步。
第三步:如果第二步結束後沒有人,那就沒有名人。如果剩下一人非A非B,是C,問C是否認識第一步中被刪除的人。如果C說不認識,問第一步裡被刪除的人是否認識C。如果被刪之人說認識,那麼C是名人。其餘情況皆是沒有名人。如果剩下一人是B,問B是否認識A,如果B不認識,B是名人,否則沒有名人。如果剩下一人是A,問B是否認識A,如果B認識,A是名人,否則沒有名人。
在最差情況下:遞推關係S(n)=S(n-1)+3當n>2,S(1)=0,S(2)=2。解得S(n)=2+3(n-2)當n>1,S(1)=0.
習題2.5
2. 斐波那契兔子問題 一個人把一對兔子用圍牆圍住。如果最初的一對兔子(一雌一雄)是新生的,並且所有的兔子在出生後的第一個月都不能繁殖,但是在之後的每個月末都能生出一對兔子(一雌一雄),那麼一年後圍牆裡將會有多少對兔子?
解答:F(n)=F(n-1)+F(n-2),當n>1。F(0)=F(1)=1。解得F(12)=233。共有233對兔子
3. 爬梯子 假設每一步可以爬一格或者兩格梯子,爬一部n格梯子一共可以用幾種方法?
解答:F(1)=1, F(2)=2, F(n)=F(n-1)+F(n-2)當n>2。可知F(n)=Fib(n+1)。
11. 分解斐波那契矩形 給定一個矩形,它的邊長是兩個連續的斐波那契數。設計一個演算法來把它分解成正方形,且具有相同尺寸的正方形不超過兩個。你的演算法的時間效率型別是什麼?
解答:不斷地將兩個邊長數字相減,每次相減分割出一個正方形(邊長為較小數),得到的數列即為逆序的斐波那契數列,演算法的時間效率為θ(n)。