蠻力法
蠻力法是基於計算機運算速度快這一特性,在解決問題時采取的一種“懶惰”的策略。這種策略不經過(或者說是經過很少的)思考,把問題的所有情況或所有過程交給計算機去一一嘗試,從中找出問題的解。蠻力策略的應用很廣,具體表現形式各異,數據結構課程中學習的:選擇排序、冒泡排序、插入排序、順序查找、朴素的字符串匹配等,都是蠻力策略具體應用。比較常用還有枚舉法、盲目搜索算法等。
枚舉法
枚舉( enumerate)法(窮舉法)是蠻力策略的一種表現形式,也是一種使用非常普遍的思維方法。它是根據問題中的條件將可能的情況一一列舉出來,逐一嘗試從中找出滿足問題條件的解。但有時一一列舉出的情況數目很大,如果超過了我們所能忍受的范圍,則需要進一步考慮,排除一些明顯不合理的情況,盡可能減少問題可能解的列舉數目。
用枚舉法解決問題,通常可以從兩個方面進行算法設計:
1)找出枚舉范圍:分析問題所涉及的各種情況。
2)找出約束條件:分析問題的解需要滿足的條件,並用邏輯表達式表示。
【例1】百錢百雞問題。中國古代數學家張丘建在他的《算經》中提出了著名的“百錢百雞問題”:雞翁一,值錢五;雞母一,值錢三;雞雛三,值錢一;百錢買百雞,翁、母、雛各幾何?
算法設計1:
通過對問題的理解,讀者可能會想到列出兩個三元一次方程,去解這個不定解方程,就能找出問題的解。這確實是一種辦法,但這里我們要用“懶惰”的枚舉策略進行算法設計:
設x,y,z分別為公雞、母雞、小雞的數量。
嘗試范圍:由題意給定共100錢要買百雞,若全買公雞最多買100/5=20只,顯然x的取值范圍1~20之間;同理,y的取值范圍在1~33之間,z的取值范圍在1~100之間。
約束條件: x+y+z=100 且 5*x+3*y+z/3=100
算法1如下:
main( ) { int x,y,z; for(x=1;x<=20;x=x+1) for(y=1;y<=34;y=y+1) for(z=1;z<=100;z=z+1) if(100=x+y+z and 100=5*x+3*y+z/3) {print("the cock number is",x); print("the hen number is", y); print("the chick number is“,z);} }
算法分析:以上算法需要枚舉嘗試20*34*100=68000次。算法的效率顯然太低
算法設計2:
在公雞(x)、母雞(y)的數量確定后,小雞 的數量 z就固定為100-x-y,無需再進行枚舉了
此時約束條件只有一個: 5*x+3*y+z/3=100
算法2如下:
main( ) { int x,y,z; for(x=1;x<=20;x=x+1) for(y=1;y<=33;y=y+1) { z=100-x-y; if(z mod 3=0 and 5*x+3*y+z/3=100) {print(the cock number is",x); print(the hen number is", y); print(the chick number is "z);} } }
算法分析:以上算法只需要枚舉嘗試20*33=660次。實現時約束條件又限定Z能被3整除時,才會判斷“5*x+3*y+z/3=100”。這樣省去了z不整除3時的算術計算和條件判斷,進一步提高了算法的效率。
【例2】解數字迷: A B C A B
× A
D D D D D D
算法設計1:按乘法枚舉
1)枚舉范圍為:
A:3——9(A=1,2時積不會得到六位數),B:0——9,
C:0——9 六位數表示為A*10000+B*1000+C*100+A*10+B, 共嘗試700次。
2)約束條件為:
每次嘗試,先求六位數與A的積,再測試積的各位是否相同,若相同則找到了問題的解。測試積的各位是否相同比較簡單的方法是,從低位開始,每次都取數據的個位,然后整除10,使高位的數字不斷變成個位,並逐一比較。
算法1如下:
main( ) { long A,B,C,D,E,E1,F,G1,G2,i; for(A=3; A<=9; A++) for(B=0; B<=9; B++) for(C=0; C<=9; C++) { F=A*10000+B*1000+C*100+A*10+B; E=F*A; E1=E; G1=E1 mod 10; for(i=1; i<=5; i++) { G2=G1; E1=E1/10; G1= E1 mod 10; if(G1<>G2 ) break; } if(i=6) print( F,”*”,A,”=”,E); } }
算法說明1:算法中對枚舉出的每一個五位數與A相乘,結果存儲在變量E中。然后,測試得到的六位數E是否各個位的數字都相同。鑒於要輸出結果,所以不要改變計算結果,而另設變量E1,用於測試運算。
算法分析1:以上算法的嘗試范圍是A:3——9,B:0——9, C:0——9 。共嘗試800次,每次,不是一個好的算法。
算法設計2:將算式變形為除法:DDDDDD/A=ABCAB。此時只需枚舉A:3——9 D:1——9,共嘗試7*9=63次。每次嘗試,測試商的萬位、十位與除數是否相同,千位與個位是否相同,都相同時為解。
算法2如下:
main() {long A,B,C,D,E,F; for(A=3;A<=9;A++) for(D=1;D<=9;D++) { E = D*100000+D*10000+D*1000+D*100+D*10+D; if(E mod A=0) F=E\A; if(F\10000=A and (F mod 100)\10=A) if(F\1000==F mod 10) print( F,”*”,A,”=”,E); } }
【例3】獄吏問題
某國王對囚犯進行大赦,讓一獄吏n次通過一排鎖着的n間牢房,每通過一次,按所定規則轉動n間牢房中的某些門鎖, 每轉動一次, 原來鎖着的被打開, 原來打開的被鎖上;通過n次后,門鎖開着的,牢房中的犯人放出,否則犯人不得獲釋。
轉動門鎖的規則是這樣的,第一次通過牢房,要轉動每一把門鎖,即把全部鎖打開;第二次通過牢房時,從第二間開始轉動,每隔一間轉動一次;第k次通過牢房,從第k間開始轉動,每隔k-1 間轉動一次;問通過n次后,那些牢房的鎖仍然是打開的?
算法設計1:
1)用n個空間的一維數組a[n],每個元素記錄一個鎖的狀態,1為被鎖上,0為被打開。
2)用數學運算方便模擬開關鎖的技巧,對i號鎖的一次開關鎖可以轉化為算術運算:a[i]=1-a[i]。
3)第一次轉動的是1,2,3,……,n號牢房;
第二次轉動的是2,4,6,……號牢房;
第三次轉動的是3,6,9,……號牢房;
……第i次轉動的是i,2i,3i,4i,……號牢房,是起點為i,公差為i的等差數列。
4)不做其它的優化,用蠻力法通過循環模擬獄吏的開關鎖過程,最后當第i號牢房對應的數組元素a[i]為0時,該牢房的囚犯得到大赦。
算法1如下:
main1( ) {int *a,i,j,n; input(n); a=calloc(n+1,sizeof(int)); for (i=1; i<=n;i++) a[i]=1; for (i=1; i<=n;i++) for (j=i; j<=n;j=j+i) a[i]=1-a[i]; for (i=1; i<=n;i++) if (a[i]=0) print(i,”is free.”); }
算法分析:以一次開關鎖計算,算法的時間復雜度為n(1+1/2+1/3+……+1/n)=O(nlogn)。
問題分析:轉動門鎖的規則可以有另一種理解,第一次轉動的是編號為1的倍數的牢房;第二次轉動的是編號為2的倍數的牢房;第三次轉動的是編號為3的倍數的牢房;……則獄吏問題是一個關於因子個數的問題。令d(n)為自然數n的因子個數,這里不計重復的因子,如4的因子為1,2,4共三個因子,而非1,2,2,4。則d(n)有的為奇數,有的為偶數,見下表:
數學模型1:上表中的d(n)有的為奇數,有的為偶數,由於牢房的門開始是關着的,這樣編號為i的牢房,所含1——i之間的不重復因子個數為奇數時,牢房最后是打開的,反之,牢房最后是關閉的。
算法設計2:
1)算法應該是求出每個牢房編號的不重復的因子個數,當它為奇數時,這里邊的囚犯得到大赦。
2)一個數的因子是沒有規律的,只能從1——n枚舉嘗試。算法2如下:
main2( ) {int s,i,j,n; input(n); for (i=1; i<=n;i++) { s=1; for (j=2; j<=i;j=j++) if (i mod j=0) s=s+1; if (s mod 2 =1) print(i,”is free.”); } }
算法分析:
獄吏開關鎖的主要操作是a[i]=1- a[i];共執行了n*(1+1/2+1/3+……+1/n)次,時間近似為復雜度為O(n log n)。使用了n個空間的一維數組。算法2沒有使用輔助空間,但由於求一個編號的因子個數也很復雜,其主要操作是判斷i mod j是否為0,共執行了n*(1+2+3+……+n)次,時間復雜度為O(n2)。
數學模型2:仔細觀察表4-3,發現當且僅當n為完全平方數時,d(n)為奇數;這是因為n的因子是成對出現的,也即當n=a*b且a≠b時,必有兩個因子a,b; 只有n為完全平方數,也即當n=a2時, 才會出現d(n)為奇數的情形。
算法設計3:這時只需要找出小於n的平方數即可。
main3( ) {int s,i,j,n; input(n); for (i=1;i<=n;i++) if(i*i<=n) print(i*i,”is free.”); else break; }
由此算法我們應當注意:在對運行效率要求較高的大規模的數據處理問題時,必須多動腦筋找出效率較高的數學模型及其對應的算法。