我是靠谱客的博主 风中乌龟,最近开发中收集的这篇文章主要介绍24点游戏(Naive solutions),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

24点游戏大家都很熟悉,具体题目可以参考编程之美1.16(P100)。根据书上的描述,给玩家4张牌,每张牌的面值在1~13之间,允许其中有数值相同的牌。采用加、减、乘、除四则运算,允许中间运算存在小数,并且可以使用括号,但每张牌只能使用一次,尝试构造一个表达式,使其运算结果为24。

输入:n1 n2 n3 n4

输出:若能得到运算结果为24,则输出一个对应的运算表达式。


对于这个问题的求解,最直接的思想(naive solutions)是枚举所有情况,四个位置的数字需要全排列枚举(4!),三个位置的加、减、乘、除运算符需要全部枚举(4^3),再加上五种括号的情况:

((n1, n2), n3), n4;  (n1, (n2, n3)), n4;  (n1, n2), (n3, n4);  n1, ((n2, n3), n4);  n1, (n2, (n3, n4))。所以, 给定任意四个数,需要枚举的情况一共有4! * 4^3 * 5 = 7680种。


看编程之美之前,我用深度优先搜索(DFS)来递归枚举所有情况, 最后得到符合条件的表达式。这样的编程思路较为清晰,但是写起来比较烦。如下:

我用dfs1()来深搜枚举四个位置的数字,dfs2()来深搜枚举加、减、乘、除运算符(枚举对应的下标后运算)。比较繁琐的是在dfs2()搜到最后一层的时候要把五种括号的情况都进行一次计算,然后把满足运算结果为24的表达式输出。在我的程序中,对应的表达式输出到一个vector<string> ss的容器中,最后利用unique()来去除重复的表达式,最后打印。

具体的代码如下:

//24points
#include<iostream>
#include<string>
#include<vector>
#include<cmath>
using namespace std;
const int MaxN=1000;
const double eps=1e-6;
int a[MaxN];
int num[MaxN];//Number stored here;
int op[MaxN];//the index of operator stored here;
string strop[MaxN];
int v[MaxN];
vector<string> ss;//results strord here;
//dont know how to use itoa(char*, int , int) here;
string tostr(int x)
{
string s="";
while(x)
{
int num=x%10;
char ch=num+'0';
s=ch+s;
x=x/10;
}
return s;
}
void outit1()
{
string s;
s=tostr(a[1]);
for(int i=1; i<=3; i++)
{
if(i < 3) s='('+s;
s=s+strop[op[i]]+tostr(a[i+1]);
if(i < 3) s=s+')';
}
ss.push_back(s);
}
void outit2()
{
string s1='('+tostr(a[1])+strop[op[1]]+tostr(a[2])+')';
string s2='('+tostr(a[3])+strop[op[3]]+tostr(a[4])+')';
string s=s1+strop[op[2]]+s2;
ss.push_back(s);
}
void outit3()
{
string s1='('+tostr(a[2])+strop[op[2]]+tostr(a[3])+')';
string s2='('+tostr(a[1])+strop[op[1]]+s1+')';
string s=s2+strop[op[3]]+tostr(a[4]);
ss.push_back(s);
}
void outit4()
{
string s;
s=tostr(a[4]);
for(int i=3; i>=1; i--)
{
if(i>1) s=s+')';
s=tostr(a[i])+strop[op[i]]+s;
if(i>1) s='('+s;
}
ss.push_back(s);
}
void outit5()
{
string s1='('+tostr(a[2])+strop[op[2]]+tostr(a[3])+')';
string s2='('+s1+strop[op[3]]+tostr(a[4])+')';
string s=tostr(a[1])+strop[op[1]]+s2;
ss.push_back(s);
}
double run(double x, double y, double z)
{
if(z == 1) return x+y;
if(z == 2) return x-y;
if(z == 3) return x*y;
if(z == 4)
{
if(fabs(y) > eps) return x/y;
else return 100000000.0000;
}
}
void dfs2(int dep)
{
if(dep > 3)
{
double t1=run(a[1],a[2],op[1]);
double t2=run(t1,a[3],op[2]);
double t3=run(t2,a[4],op[3]);
if(fabs(t3-24) < eps) outit1();
t1=run(a[1],a[2],op[1]);
t2=run(a[3],a[4],op[3]);
t3=run(t1,t2,op[2]);
if(fabs(t3-24) < eps) outit2();
t1=run(a[2],a[3],op[2]);
t2=run(a[1],t1,op[1]);
t3=run(t2,a[4],op[3]);
if(fabs(t3-24) < eps) outit3();
t1=run(a[3],a[4],op[3]);
t2=run(a[2],t1,op[2]);
t3=run(a[1],t2,op[1]);
if(fabs(t3-24) < eps) outit4();
t1=run(a[2],a[3],op[2]);
t2=run(t1,a[4],op[3]);
t3=run(a[1],t2,op[1]);
if(fabs(t3-24) < eps) outit5();
return;
}
for(int i=1; i<=4; i++)
{
op[dep]=i;
dfs2(dep+1);
}
}
void dfs1(int dep)
{
if(dep > 4)
{
dfs2(1);
return;
}
for(int i=1; i<=4; i++)
{
if(!v[i])
{
v[i]=1;
a[dep]=num[i];
dfs1(dep+1);
v[i]=0;
}
}
}
int main()
{
ss.clear();
strop[1]='+';
strop[2]='-';
strop[3]='*';
strop[4]='/';
for(int i=1; i<=4; i++) cin>>num[i];
dfs1(1);
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;
} 

看了编程之美后,发现编程之美的第一种解法也是枚举所有情况,应该说时间复杂度上是一样的,但是明显代码比我写的简洁。书上的解法的思路是分治递归,通过取集合中的两个数,进行相应的加、减、乘、除运算后,将新的数放入集合,这样问题的规模就减少成了三个数,然后再取两个数,直到集合中只有一个数字为止。然后判断是否为24并返回。要注意的是,要取遍集合中的任意两个数,而且每次取完后要记得放回到原集合中,再取下一对数。具体的描述还有伪代码编程之美写的比较好,就不赘述了。下面是按照书上的第一种思路实现后的C++代码:
//24points BOP(1)
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
using namespace std;
const double eps=1e-6;
const int MaxN=100;
double num[MaxN];
string str[MaxN];
vector<string> ss;
void point(int n)
{
if(n==1)
{
if(fabs(num[1]-24) < eps)
{
ss.push_back(str[1]);
//cout<<str[1]<<endl;
return;
}
else
return;
}
for(int i=1; i<=n; i++)
{
for(int j=i+1; j<=n; j++)
{
double a,b;
string expa,expb;
//temporarily stored here;
a=num[i];
b=num[j];
expa=str[i];
expb=str[j];
//shift left
num[j]=num[n];
str[j]=str[n];
str[i]='('+expa+'+'+expb+')';
num[i]=a+b;
point(n-1);
str[i]='('+expa+'-'+expb+')';
num[i]=a-b;
point(n-1);
str[i]='('+expb+'-'+expa+')';
num[i]=b-a;
point(n-1);
str[i]='('+expa+'*'+expb+')';
num[i]=a*b;
point(n-1);
if(b!=0)
{
str[i]='('+expa+'/'+expb+')';
num[i]=a/b;
point(n-1);
}
if(a!=0)
{
str[i]='('+expb+'/'+expa+')';
num[i]=b/a;
point(n-1);
}
num[i]=a;
num[j]=b;
str[i]=expa;
str[j]=expb;
}
}
}
int main()
{
ss.clear();
double x;
for(int i=1; i<=4; i++)
{
cin>>x;
char buffer[MaxN];
num[i]=x;
itoa(x,buffer,10);
str[i]=buffer;
}
point(4);
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;
} 

上面两段代码运行的结果是一样的,下面用书后的几个测试用例来进行测试:

测试用例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))

 

我们可以发现第一个和第二个测试用例的结果中有很多表达式其实是一样的,怎么样才能去除这样的结果呢?有待思考和请教!


上面的两种都是直接的解法,效率不是很高,枚举的时候有很的冗余计算,需要怎么提高和改进?看了编程之美的第二种解法后,我觉得书上的思路非常不错,利用了集合和动态规划的思想,合理的利用了先前的计算结果,逐步求解。书上的伪代码也很清晰,我自己实现的代码将贴在下一篇文章中,欢迎指正!





最后

以上就是风中乌龟为你收集整理的24点游戏(Naive solutions)的全部内容,希望文章能够帮你解决24点游戏(Naive solutions)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(67)

评论列表共有 0 条评论

立即
投稿
返回
顶部