接上一篇文章,这篇文章将介绍24点游戏的一种改进的编程方法,将减少枚举过程的冗余。思路是编程之美上的解法二,思想是集合和动态规划(set & DP),利用了子集的结果求出更大集合的解,最后求出整个集合上面的的所有表达式,再进行筛选。书上的文字解释写的不太清楚,需要结合伪代码看,所以在此就把书上的伪代码拷贝过来,再附上自己具体实现的C++代码,欢迎大家指正!
伪代码如下:
24Game(Array) // Array为初始输入的集合,其中元素表示为ai(0<=i<=n-1)
{
for(int i = 1; i < = 2n - 1; i++)
S[i] = ; // 初始化将S中各个集合置为空集,n为集合Array的元素个数,
// 在24点中即为4,后面出现的n具相同含义
for(int i = 0; i < n; i++)
S[2i] = {ai}; // 先对每个只有一个元素的真子集赋值,即为该元素本身
for(int i = 1; i < = 2n - 1; i++) // 每个i都代表着Array的一个真子集
S[i] = f(i);
Check(S[2n - 1]); // 检查S[2n-1]中是否有值为24的元素,并返回
}
f(int i) // i的二进制表示可代表集合的一个真子集,具体含义见上面的分析
{
if(S[i] )
return S[i];
for(int x = 1; x < i; i++) // 只有小于i的x才可能成为i的真子集
if(x & i == x)// &为与运算,只有当x&i==x成立时x才为i的子集,此时i-x为i的
// 另一个真子集,x与i-x共同构成i的一个划分,读者可自行验证
S[i] = Fork(f(x), f(i-x)); // 为集合的并运算,Fork见
// 定义1-16-1,在Fork的过程中,
// 去除重复中间结果……
}
实现的C++代码如下:
//24points BOP(2)
#include<iostream>
#include<string>
#include<vector>
#include<cmath>
using namespace std;
const int MaxN=1000;
const int CardN=4;
const double eps=1e-6;
typedef struct ans* link;
struct ans
{
double data;
string str;
};
int num[MaxN];
vector<ans> result[MaxN];
vector<string> ss;
void check(vector<ans> x)
{
for(int i=0; i<=x.size(); i++)
{
if(fabs(x[i].data - 24) < eps) ss.push_back(x[i].str);
}
}
vector<ans> joint(vector<ans> x, vector<ans> y)
{
vector<ans> z;
z.clear();
for(int i=0; i<x.size();i++)
{
for(int j=0; j<y.size(); j++)
{
link temp=new ans;
(*temp).data=x[i].data+y[j].data;
(*temp).str='('+x[i].str+'+'+y[j].str+')';
z.push_back(*temp);
link temp1=new ans;
(*temp1).data=x[i].data-y[j].data;
(*temp1).str='('+x[i].str+'-'+y[j].str+')';
z.push_back(*temp1);
link temp2=new ans;
(*temp2).data=y[j].data-x[i].data;
(*temp2).str='('+y[j].str+'-'+x[i].str+')';
z.push_back(*temp2);
link temp3=new ans;
(*temp3).data=x[i].data*y[j].data;
(*temp3).str='('+x[i].str+'*'+y[j].str+')';
z.push_back(*temp3);
if(y[j].data > eps)
{
link temp4=new ans;
(*temp4).data=x[i].data/y[j].data;
(*temp4).str='('+x[i].str+'/'+y[j].str+')';
z.push_back(*temp4);
}
if(x[i].data > eps)
{
link temp5=new ans;
(*temp5).data=y[j].data/x[i].data;
(*temp5).str='('+y[j].str+'/'+x[i].str+')';
z.push_back(*temp5);
}
}
}
return z;
}
vector<ans> f(int i)
{
vector<ans> res;
res.clear();
if(!result[i].empty()) return result[i];
for(int j=1; j<i; j++)
{
if((j&i) == j)
{
vector<ans> tmp;
tmp.clear();
tmp=joint(f(j), f(i-j));
res.insert(res.end(), tmp.begin(), tmp.end());
}
}
return res;
}
void point()
{
for(int i=1; i<=((1<<CardN)-1); i++) result[i].clear();
//initialize the sets with one element;
for(int i=1; i<=CardN; i++)
{
char buffer[MaxN];
itoa(num[i], buffer, 10);
link temp=new ans;
(*temp).data=num[i];
(*temp).str=buffer;
result[1<<(i-1)].push_back(*temp);
}
for(int i=1; i<=((1<<CardN)-1); i++) result[i]=f(i);
check(result[(1<<CardN)-1]);
}
int main()
{
ss.clear();
int x;
for(int i=1; i<=4; i++)
{
cin>>x;
num[i]=x;
}
point();
sort(ss.begin(),ss.end());
vector<string>::iterator it=unique(ss.begin(), ss.end());
for(vector<string>::iterator it1=ss.begin(); it1!=it; it1++) cout<<*it1<<endl;
system("pause");
return 0;
}
测试结果和上一篇文章中的两个naive solutions的结果是一样的,如下:
测试用例1:
Input:
5 5 5 1
Output:
(5-(1/5))*5
5*(5-(1/5))
测试用例2:
Input:
3 3 7 7
Output:
((3/7)+3)*7
(3+(3/7))*7
7*((3/7)+3)
7*(3+(3/7))
测试用例3:
Input:
3 3 8 8
Output:
8/(3-(8/3))
To be continued...