24點是一種老少皆宜的遊戲,它的具體玩法如下:
給玩家4張牌,每張牌的面值都在1---13之間,允許其中有數值相同的牌。採用加、減、乘、除四則運算,允許中間運算存在小數,並且可以使用括弧,但每張牌只能使用一次,嘗試構造一個多項式,使其運算結果為24.
輸入:n1,n2,n3,n4。
輸出:若能得到運算結果為24,則輸出一個對應的計算運算式。
輸入:11,8,3,5
輸出:(11-8)*(3+5) = 24
分析與解法
最直接的想法就是採用窮舉法,因為運算子號只有4種,每個數字只能使用一次,所以通過窮舉4個數所有可能的運算式,並分別計算出各運算式的值,就可以得到答案。那麼如何窮舉所有可能的運算式呢?
先不考慮使用括弧,可以做出如下分析:
每個數只能使用一次,對4個整數進行全排列,總共有4!=4*3*2*1=24中排列。4個數的四則運算中總共需要3個運算子,同一運算子可以重複出現,那麼對於每一個排列,總共可有4*4*4中運算式。因此在不考慮括弧的情況下,總共可以得到4!*4*4*4=1536中運算式。
接下來再考慮加上括弧後的情況,對於4個數而言,總共會有以下5種加括弧的方式:
(A(B(CD)))、(A((BC)D))、((AB)(CD))、((A(BC))D)、(((AB)C)D)。
所以需要遍曆的運算式數最多有4!*4*4*4*5=7680種。即使採用逆波蘭運算式,其總數也不變。
通過上面的分析,得到了一種解24點的基本思路,即遍曆運算子、數字和括弧的所有排列組合形式,接下來,我們將更加細緻地討論這種解法的一個具體實現。
假設給定的4個數組成的集合為A={1,2,3,4},定義函數f(A)為對集合A中的元素進行所有可能的四則混合運算所得到的值。
首先從集合A中任意取出兩個數,如取出1和2,A=A-{1,2},對取出來的數分別進行不同的四則運算,1+2=3,1-2=-1,1/2=0.5,1*2=2,將得到的結果再分別加入集合A,可得到B={3,3,4},C={-1,3,4},D={0.5,3,4},E={2,3,4}四個新的集合,那麼f(A)= f(B)+f(C)+f(D)+f(E),通過以上的計算就達到了分而治之的目的,問題規模就從4個數降到了3個數,成了3個數的4個子問題之和。綜上所述,可以得到遞迴解法如下:
將給定的4個數放入數組Array中,將其作為參數傳入函數f中,虛擬碼如下所示:
f(Array){if(Array.Length < 2){if(得到的最終結果為)輸出運算式else 輸出無法構造符合要求的運算式}foreach(從數組中任取兩個數的組合){foreach(運算子(+, -, *, /)){1、計算該組合在此運算子下的結果2、將該組合中的兩個數從原數組中移除,並將步驟的計算結果放入數組3、對新數組遞迴調用f。如果找到一個運算式則返回4、將步驟的計算結果移除,並將該組合中的兩個數重新放回數組中對應的位置}}}
具體代碼如下所示:
#include "iostream"#include "cmath"#include "string"using namespace std;const double Threshold = 1E-6; //容差值const int CardsNumber = 4;const int ResultValue = 24;double number[CardsNumber]; //輸入的4個整數string result[CardsNumber]; //儲存運算式bool PointsGame(int n){if(n == 1){//由於浮點數運算會有精度誤差,所以用一個很小的數1E-6來做容差值if(fabs(number[0] - ResultValue) < Threshold){cout << result[0] << " == 24" << endl;return true;}else{return false;}}for(int i = 0; i < n; i++){for(int j = i + 1; j < n; j++) //從數組中任取兩個數的組合{double a, b;string expa, expb;a = number[i];b = number[j];number[j] = number[n-1]; //任取兩個數並將其結果放入數組後,原數組的個數減少一個expa = result[i]; //儲存運算式中的數字expb = result[j];result[j] = result[n-1];result[i] = '(' + expa + '+' + expb + ')'; //任取的兩個數在“+”運算子下的結果number[i] = a + b; //將該運算子下的運算結果放入數組中if(PointsGame(n - 1))return true;result[i] = '(' + expa + '-' + expb + ')'; //任取的兩個數在“-”運算子下的結果number[i] = a - b;if(PointsGame(n - 1))return true;result[i] = '(' + expb + '-' + expa + ')'; //任取的兩個數在“-”運算子下的結果number[i] = b - a;if(PointsGame(n - 1))return true;result[i] = '(' + expa + '*' + expb + ')'; //任取的兩個數在“*”運算子下的結果number[i] = a * b;if(PointsGame(n - 1))return true;if(b != 0){result[i] = '(' + expa + '/' + expb + ')'; //任取的兩個數在“/”運算子下的結果number[i] = a / b;if(PointsGame(n - 1))return true;}if(a != 0){result[i] = '(' + expb + '/' + expa + ')'; //任取的兩個數在“/”運算子下的結果number[i] = b / a;if(PointsGame(n - 1))return true;}number[i] = a; //將剛開始任取的兩個數重新放回數組中對應的位置number[j] = b;result[i] = expa;result[j] = expb;}}return false;}int main(void){int i,x;for(i = 0; i < CardsNumber; i++){char buffer[20];cout << "Please input the " << i+1 << "th number:";cin >> x;number[i] = x; //儲存輸入的4個整數的浮點型數組itoa(x, buffer, 10); //把輸入的4個整數按10進位轉化為字串result[i] = buffer;}if(PointsGame(CardsNumber) ){cout << "Success." << endl;}else{cout << "Fail." << endl;}system("pause");return 0;}
運行如下: