8算法策略之枚舉法


蠻力法

蠻力法是基於計算機運算速度快這一特性,在解決問題時采取的一種“懶惰”的策略。這種策略不經過(或者說是經過很少的)思考,把問題的所有情況或所有過程交給計算機去一一嘗試,從中找出問題的解。蠻力策略的應用很廣,具體表現形式各異,數據結構課程中學習的:選擇排序、冒泡排序、插入排序、順序查找、朴素的字符串匹配等,都是蠻力策略具體應用。比較常用還有枚舉法、盲目搜索算法等。

 

枚舉法

枚舉( 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;
 }

  由此算法我們應當注意:在對運行效率要求較高的大規模的數據處理問題時,必須多動腦筋找出效率較高的數學模型及其對應的算法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM