窮舉是用計算機求解問題最常用的方法之一,常用來解決那些通過公式推導、規則演繹的方法不能解決的問題。采用窮舉法求解一個問題時,通常先建立一個數學模型,包括一組變量、以及這些變量需要滿足的條件。問題求解的目標就是確定這些變量的值。根據問題的描述和相關的知識,能為這些變量分別確定一個大概的取值范圍。在這個范圍內對變量依次取值,判斷所取的值是否滿足數學模型中的條件,直到找到全部符合條件的值為止。
窮舉法(枚舉法)的基本思想是:列舉出所有可能的情況,逐個判斷有哪些是符合問題所要求的條件,從而得到問題的全部解答。
它利用計算機運算速度快、精確度高的特點,對要解決問題的所有可能情況,一個不漏地進行檢查,從中找出符合要求的答案。
用窮舉算法解決問題,通常可以從兩個方面進行分析。
(1)問題所涉及的情況:問題所涉及的情況有哪些,情況的種數可不可以確定。把它描述出來。應用窮舉時對問題所涉及的有限種情形必須一一列舉,既不能重復,也不能遺漏。重復列舉直接引發增解,影響解的准確性;而列舉的遺漏可能導致問題解的遺漏。
(2)答案需要滿足的條件:分析出來的這些情況,需要滿足什么條件,才成為問題的答案。把這些條件描述出來。
只要把這兩個方面分析好了,問題自然會迎刃而解。
窮舉通常應用循環結構來實現。在循環體中,根據所求解的具體條件,應用選擇結構實施判斷篩選,求得所要求的解。
窮舉法的程序框架一般為:
cnt=0; // 解的個數初值為0
for(k=<區間下限>;k<=<區間上限>;k++) // 根據指定范圍實施窮舉
if (<約束條件>) // 根據約束條件實施篩選
{
cout<<(<滿足要求的解>); // 輸出滿足要求的解
cnt++; // 統計解的個數
}
【例1】硬幣方案
有50枚硬幣,可能包括4種類型:1元、5角、1角和5分。
已知50枚硬幣的總價值為20元,求各種硬幣的數量。
例如:2、34、6、8就是一種方案。而2、33、15、0是另一個可能的方案,顯然方案不唯一。
編寫程序求出類似這樣的不同的方案一共有多少種?
(1)編程思路。
直接對四種類型的硬幣的個數進行窮舉。其中,1元最多20枚、5角最多40枚、1角最多50枚、5分最多50枚。
另外,如果以元為單位,則5角、1角、5分會化成浮點型數據,容易計算出錯。可以將1元、5角、1角、5分變成100分、50分、10分和5分,從而全部采用整型數據處理。
(2)源程序及運行結果。
#include <iostream>
using namespace std;
int main()
{
int a,b,c,d,cnt=0;
for(a=0;a<=20;a++)
for(b=0;b<=40;b++)
for(c=0;c<=50;c++)
for(d=0;d<=50;d++)
{
if(a*100+b*50+c*10+d*5==2000 && a+b+c+d==50)
{
cout<<a<<" , "<<b<<" , "<<c<<" , "<<d<<endl;
cnt++;
}
}
cout<<"Count="<<cnt<<endl;
return 0;
}
(3)程序的優化。
上面的程序采用窮舉法求解,比較簡單。但在窮舉結構的設置、窮舉參數的選取等方面存在着改進與優化的空間。
一般來說,在采用窮舉法進行問題求解時,可從兩個方面來優化考慮。
1)建立簡潔的數學模型。
數學模型中變量的數量要盡量少,它們之間相互獨立。這樣問題解的搜索空間的維度就小。反應到程序代碼中,循環嵌套的層次就少。例如,上面的程序中,采用變量a、b、c、d分別表示1元、5角、1角和5分硬幣的枚數,對這4個變量窮舉,循環層次為4層。實際上這4個變量彼此間有兩個條件在約束,或者枚數等於50,或者總價值為20元。因此,可以只窮舉3個變量,另外一個變量通過約束條件求出,從而將循環層次減少為3層。
2)減小搜索的空間。
利用已有的知識,縮小數學模型中各個變量的取值范圍,避免不必要的計算。反應到程序代碼中,循環體被執行的次數就減少。例如,在窮舉時,先考慮1元的枚數a,最多為20枚(即0<=a<=20),再考慮5角的枚數b,若采用總價值不超過20元約束,則其枚數最多為(2000-a*100)/50枚(即0<=b<=(2000-a*100)/50),之后考慮1角的枚數c,其枚數最多為 (2000-a*100-b*50)/10(即0<=c<=(2000-a*100-b*50)/10)。這樣窮舉的循環次數會大大減少。
采用上述思路優化后的源程序如下。
#include <iostream>
using namespace std;
int main()
{
int a,b,c,d,cnt=0;
for(a=0;a<=20;a++)
for(b=0;b<=(2000-a*100)/50;b++)
for(c=0;c<=(2000-a*100-b*50)/10;c++)
{
d=(2000-a*100-b*50-c*10)/5; // 剩下的用5分硬幣填充
if(a+b+c+d==50)
{
cout<<a<<" , "<<b<<" , "<<c<<" , "<<d<<endl;
cnt++;
}
}
cout<<"Count="<<cnt<<endl;
return 0;
}
也可以采用總枚數不超過50枚約束。先考慮1元的枚數a,最多為20枚(即0<=a<=20),再考慮5角的枚數b,則其枚數最多為(50-a)枚(即0<=b<=(50-a),之后考慮1角的枚數c,其枚數最多為 (50-a-b)枚(即0<=c<=50-a-b)。采用這種思路優化后的源程序如下。
#include <iostream>
using namespace std;
int main()
{
int a,b,c,d,cnt=0;
for(a=0;a<=20;a++)
for(b=0;b<=50-a;b++)
for(c=0;c<=50-a-b;c++)
{
d=50-a-b-c; // 剩下的用5分硬幣填充
if(100*a+50*b+10*c+5*d==2000)
{
cout<<a<<" , "<<b<<" , "<<c<<" , "<<d<<endl;
cnt++;
}
}
cout<<"Count="<<cnt<<endl;
return 0;
}