經典二十四點程序算法
來源:http://www.xici.net/d190569991.htm
一、概述
算24點:任意給定四個整數,用加、減、乘、除以及適當的括號連接,無論順序,使計算結果爲24,也可能根本就無解。如何用程序簡單實現找到算式是程序初學者關注的問題,百度上可以搜到許多這樣的文章,有遞歸法、回溯法、窮舉法。但窮舉法最爲簡單、易於理解。
二、算法
窮舉法就是把四個數字的所有運算式進行嘗試計算,要設法把所有排列一點不差地窮舉出,一、是四個整數位置的排列,用0,1,2,3表示位置,排列是不能重複的,所以有P(4,4)種情況,即4!=4 * 3 * 2 * 1=24種;二、是三個運算符的變化,每個運算符爲±/ ,可以相同,所以,有4 * 4 * 4=64種; 三、三個運算符的優先級,就是括號位置的變化,可能性爲P(3,3)-1=6-1=5種;所以,表達式的可能性爲:24 * 64 * 5=7680種,C語言程序把這麼多表達式都計算一遍,幾乎不到1毫秒毫不費勁, 可見電腦的運行速度之快。
四個數位置的排列,可用四重循環實現,四個循環變量i1,i2,i3,i4對應數組下標的變化,
不許它們之間相等,四個數放在數組v[0]、v[1]、v[2]、v[3]:
for (int i1=0;i1<4;i1++)
for (int i2=0;i2<4;i2++)
if (i2!=i1) // 循環變量不許相同
for (int i3=0;i3<4;i3++)
if (i3!=i2 && i3!=i1) // 循環變量不許相同,&& 是“並且”
for (int i4=0;i4<4;i4++)
if (i4!=i3 && i4!=i2 && i4!=i1) // 循環變量不許相同
{
// v[i1],v[i2],v[i3],v[i4] 就是排列的結果,共有4!=24 種
}
三個運算符的變化,可用三重循環實現,三個循環變量 f1,f2,f3對應位置的變化,允許相同,運算符放在數組op[0]、op[1]、op[2]、op[3]中:
for (int f1=0;f1<4;f1++) // 三重循環對運算符窮舉
for (int f2=0;f2<4;f2++)
for (int f3=0;f3<4;f3++) // 運算符 f1,f2,f3
{
// op[f1],op[f2],op[f3]就是排列結果,共有44*4=64 種
}
上面兩種排列組合後,四個數及運算符排列順序就是:
v[i1],op[f1],v[i2],op[f2],v[i3],op[f3],v[i4]
但運算符有優先級,一般是用括號表示。我們可以規定運算符的優先級來代替括號。設四張牌爲a、b、c、d,運算符爲①、②、③,表達式爲a ① b ② c ③ d。 這3個運算符的運算順序有3!=6種,分別是:
1.①②③ 2.①③② 3.②①③ 4.②③① 5.③①② 6.③②①
等價的表達式分別是:
1.((a①b②)c③)d 2.(a①b)②(c③d) 3.(a①(b②c))③d
4.a①((b②c)③d) 5.(a①b)②(c③d) 6. a①(b②(c③d))
顯然,2和5是相同的,因此只考慮5種情況。這樣,括號的問題就解決了。爲了簡單起見,這五種情況未用循環,大大減化了程序的複雜性,更易於理解。
對這組排列逐一進行運算,看是否是24,就可以得到最終所有式子了。在運算過程中除法的特殊性——除數不能爲零。因爲可能會用到除法,所以要考慮精度問題,這裏通過結果減去24取絕對值與一個接近0的小數(如0.001)比較,如小於它,即可判定結果是24。
#include "stdio.h" // 輸入輸出定義
double cal(double a,double b,int op) // op: 0:+,1:-,2:*,3:/
{
switch (op) // +-x/ 運算
{
case 0: return(a+b);
case 1: return(a-b);
case 2: return(a*b);
}
if (b==0.0) // 分母爲0
return(999.0);
else
return(a/b);
}
bool isEqual(double d1,double d2) // 兩個浮點數是否近似相等
{
double d=d1-d2;
if (d<0)
d=-d; // 求絕對值
return(d<=0.001);
}
void D24(int v0,int v1,int v2,int v3) // 窮舉法求24點
{
char op[4]={'+','-','*','/'}; // +:0 -:1 *:2 /:3
int v[4]; // 輸入四整數
v[0]=v0; v[1]=v1;
v[2]=v2; v[3]=v3;
//-----------四重循環開始窮舉四個數字的位置: 4!=24 種--------------------------
int count=0; // 計數成功次數
for (int i1=0;i1<4;i1++)
for (int i2=0;i2<4;i2++) // 四重循環對四個數窮舉
if (i2!=i1)
for (int i3=0;i3<4;i3++)
if (i3!=i2 && i3!=i1)
for (int i4=0;i4<4;i4++)
if (i4!=i3 && i4!=i2 && i4!=i1)
{
//-----------三重循環開始窮舉三個運算符: 4X4X4=64 種---------------------------
for (int f1=0;f1<4;f1++) // 三重循環對運算符窮舉
for (int f2=0;f2<4;f2++)
for (int f3=0;f3<4;f3++) // 運算符 f1,f2,f3
{ // 對運算優先級直接列舉(5種)
//-----------未用循環,直接窮舉三個運算符的優先級: 3!-1=5種--------------------
double t1,t2,t3; // 存放計算的中間值
// 第1種情況 ((a 。b)。c)。d :
t1=cal(v[i1],v[i2],f1);
t2=cal(t1,v[i3],f2);
t3=cal(t2,v[i4],f3);
if (isEqual(t3,24)) // 運算後是否爲24
{
char *fs="((%d%c%d)%c%d)%c%d=24\\n";
printf(fs,v[i1],op[f1],v[i2],op[f2],v[i3],op[f3],v[i4]);
count++;
}
// 第2種情況(a 。b)。(c。 d) 開始計算 :
t1=cal(v[i1],v[i2],f1);
t2=cal(v[i3],v[i4],f3);
t3=cal(t1,t2,f2);
if (isEqual(t3,24)) // 運算後是否爲24
{
char *fs="(%d%c%d)%c(%d%c%d)=24\\n";
printf(fs,v[i1],op[f1],v[i2],op[f2],v[i3],op[f3],v[i4]);
count++;
}
// 第3種情況 (a。(b。c))。d 開始計算 :
t1=cal(v[i2],v[i3],f2);
t2=cal(v[i1],t1,f1);
t3=cal(t2,v[i4],f3);
if (isEqual(t3,24)) // 運算後是否爲24
{
char *fs="(%d%c(%d%c%d))%c%d=24\\n";
printf(fs,v[i1],op[f1],v[i2],op[f2],v[i3],op[f3],v[i4]);
count++;
}
// 第4種情況 a。((b。c)。d ) 開始計算:
t1=cal(v[i2],v[i3],f2);
t2=cal(t1,v[i4],f3);
t3=cal(v[i1],t2,f1);
if (isEqual(t3,24)) // 運算後是否爲24
{
char *fs="%d%c((%d%c%d)%c%d)=24\\n";
printf(fs,v[i1],op[f1],v[i2],op[f2],v[i3],op[f3],v[i4]);
count++;
}
// 第5種情況 a。(b。(c。d)) 開始計算:
t1=cal(v[i3],v[i4],f3);
t2=cal(v[i2],t1,f2);
t3=cal(v[i1],t2,f1);
if (isEqual(t3,24)) // 運算後是否爲24
{
char *fs="%d%c(%d%c(%d%c%d))=24\\n";
printf(fs,v[i1],op[f1],v[i2],op[f2],v[i3],op[f3],v[i4]);
count++;
}
}
}
//-------------- 窮舉結束: 共 24*64*5=7680 種表達式 ---------------------------
if (count==0)
printf("\\n%d,%d,%d,%d 不能算出24.",v0,v1,v2,v3);
else
printf("\\n共找到算式 %d 條.",count);
}
void main()
{
int v0,v1,v2,v3;
printf("輸入四個整數: ");
scanf("%d %d %d %d",&v0,&v1,&v2,&v3);
D24(v0,v1,v2,v3);
getchar();
getchar();
}