一个24点算法

tottimax 2008-05-29 08:12:38
下面是一个24点游戏的算法,虽然注释已经很详细了,但是我任然看不懂,哪位高手能写个该算法的分析说明,不胜感谢!!


package com.xm;
/** 给定4个数字计算24 */
public class Core {

private double expressionResult=24;
// private int maxLine=10;
private boolean error=true;
private double numbers[]=new double[4];
public Object resultReturn;


/** 该对象拥有3个私有变量
expressionResult,所需结果
maxLine,输出结果每页行数
error,是否出错
numbers[4],记录用来运算的4个数

其次,该对象拥有以下方法供外部调用
setNumbers(double[] <运算的数>)
输入用来运算的数,4个时才能计算,无返回
setMaxLine(int <行数>)
输入每页的行数,无返回
getMaxLine()
返回每页的行数,类型为int
setExpressionResult(double <所需结果>)
输入所需结果,无返回
getExpressionResult()
返回所需结果,类型为double
getExpression()
返回可得出所需结果的表达式,类型为字符串数组

最后,私有方法均为计算与表达式转换部分 */

//测试使用
// public static void main(String[] args) {
// Core2 s=new Core2();
// s.setNumbers(new int[]{4,4,4,4});
// String[] output=s.getExpression();
// for (int i=0; i<output.length; i++) {
// (null,output[i]);
// }
// }

/** 设定被计算的四个数,由于是数组,所以具有容错功能(不为4个数) */
public void setNumbers(double[] n) {
if (n.length==4) {
error=false;
numbers=n;
} else
error=true;
}
public void setNumbers(int[] n) {
if (n.length==4) {
error=false;
for (int i=0; i<4; i++) {
numbers[i]=n[i];
}
} else
error=true;
}

/** 设定每页显示的行数 */
// public void setMaxLine(int n) {
// if (n>0) {
// maxLine=n;
// }
// }

///** 返回每页显示的行数 */
// public int getMaxLine() {
// return maxLine;
// }

/** 设定需要得到的结果 */
public void setExpressionResult(double n) {
expressionResult=n;
}

/** 返回所需结果 */
public double expressionResult() {
return expressionResult;
}

/** 返回符合条件的表达式 */
public String[] getExpression() {
if (!error) {
String[] expression=calculate(numbers);
return expression;
} else
return new String[]{"出错了,输入有误"};
}

/** cal24(),输出结果为24的表达式 */
private String[] calculate(double[] n) {
if (n.length!=4)
return new String[]{"Error"};
double[] n1=new double[3];
double[] n2=new double[2];
String[] resultString=new String[1024] ; //最多1000组解,暂时未溢出
int count=0;
boolean isRepeat=false;
for (int t1=0; t1<6; t1++) {
for (int c1=0; c1<6; c1++) {
for (int t2=0; t2<3; t2++) {
for (int c2=0; c2<6; c2++) {
for (int c3=0; c3<6; c3++) {
if ((c1/3==c2/3 && (c1%3)*(c2%3)!=0)||(c2/3==c3/3 && (c2%3)*(c3%3)!=0)||(c1/3==c3/3 && (c1%3)*(c3%3)!=0 && t2==2)) {
//去除连减连除的解,因为x/(y/z)=x*z/y
continue;
}
n1=cal1(n,t1,c1);
n2=cal2(n1,t2,c2);
double result=cal(n2[0],n2[1],c3);
if ((result-expressionResult)<0.00000001 && (expressionResult-result)<0.00000001) {
resultString[count]=calString(n,t1,c1,t2,c2,c3)+"="+(int)expressionResult;
for (int i=0; i<count; i++) {
isRepeat=false;
if (resultString[i].equals(resultString[count])) { //去除完全重复的解
isRepeat=true;
break; //提前退出循环
}
}
if (c1==c2 && c2==c3 && c1%3==0 && t1+t2!=0) { //连加连乘
isRepeat=true;
}
if (!isRepeat) {
count++;
}
}
}
}
}
}
}
if (count==0)
return new String[]{"该组数无解"};
String[] resultReturn=new String[count];
System.arraycopy(resultString,0,resultReturn,0,count);
return resultReturn;
}

/** cal1(),将4个数计算一次后返回3个数 */
private double[] cal1(double[] n, int t, int c) { //t为原来的t1,c为原来的c1
double[] m=new double[3];
switch (t) {
case 0:
m[1]=n[2];m[2]=n[3];m[0]=cal(n[0],n[1],c);break;
case 1:
m[1]=n[1];m[2]=n[3];m[0]=cal(n[0],n[2],c);break;
case 2:
m[1]=n[1];m[2]=n[2];m[0]=cal(n[0],n[3],c);break;
case 3:
m[1]=n[0];m[2]=n[3];m[0]=cal(n[1],n[2],c);break;
case 4:
m[1]=n[0];m[2]=n[2];m[0]=cal(n[1],n[3],c);break;
default:
m[1]=n[0];m[2]=n[1];m[0]=cal(n[2],n[3],c);
}
return m;
}

/** cal2(),将3个数计算一次后返回2个数 */
private double[] cal2(double[] n, int t, int c) { //t为原来的t2,c为原来的c2
double[] m=new double[2];
switch (t) {
case 0:
m[1]=n[2];m[0]=cal(n[0],n[1],c);break;
case 1:
m[1]=n[1];m[0]=cal(n[0],n[2],c);break;
default:
m[1]=n[0];m[0]=cal(n[1],n[2],c);
}
return m;
}

/** cal(),将2个数计算后返回结果 */
private double cal(double n1, double n2, int c) { //n1,n2为运算数,c为运算类型
switch (c) {
case 0:
return n1+n2;
case 1:
return n1-n2;
case 2:
return n2-n1;
case 3:
return n1*n2;
case 4:
if (n2==0)
return 9999; //使计算结果必不为24
else
return n1/n2;
default:
if (n1==0)
return 9999; //同上
else
return n2/n1;
}
}

/** calString(),输出表达式 */
private String calString(double[] n, int t1, int c1, int t2, int c2, int c3) {
String[] nString=new String[4];
switch (t1) {
case 0:
nString[0]=calString2(""+(int)n[0],""+(int)n[1],c1);nString[1]=""+(int)n[2];nString[2]=""+(int)n[3];break;
case 1:
nString[0]=calString2(""+(int)n[0],""+(int)n[2],c1);nString[1]=""+(int)n[1];nString[2]=""+(int)n[3];break;
case 2:
nString[0]=calString2(""+(int)n[0],""+(int)n[3],c1);nString[1]=""+(int)n[1];nString[2]=""+(int)n[2];break;
case 3:
nString[0]=calString2(""+(int)n[1],""+(int)n[2],c1);nString[1]=""+(int)n[0];nString[2]=""+(int)n[3];break;
case 4:
nString[0]=calString2(""+(int)n[1],""+(int)n[3],c1);nString[1]=""+(int)n[0];nString[2]=""+(int)n[2];break;
default:
nString[0]=calString2(""+(int)n[2],""+(int)n[3],c1);nString[1]=""+(int)n[0];nString[2]=""+(int)n[1];
}
if ((c2/3>c1/3 && (t2!=2 || c2/3==c3/3)) || ((c3/3>c1/3+c2/3) && t2==2) || (c3==1 && c1/3==0)) //特定情况下加上一个括号*****************************
nString[0] = '('+nString[0]+')';
switch (t2) {
case 0:
nString[0]=calString2(nString[0],""+nString[1],c2);nString[1]=nString[2];break;
case 1:
nString[0]=calString2(nString[0],nString[2],c2);break;
default:
nString[3]=nString[0];nString[0]=calString2(nString[1],nString[2],c2);nString[1]=nString[3];
}
if (c3/3>c2/3 || (c3==2 && nString[0].indexOf('+')>=0)) //特定情况下加上一个括号*****************************
nString[0]= '('+nString[0]+')';
return calString2(nString[0],nString[1],c3);
}

/** calString(),根据符号输出一部运算表达式 */
private String calString2(String n1, String n2, int c) {
switch (c) {
case 0:
return n1+'+'+n2;
case 1:
return n1+'-'+n2;
case 2:
return n2+'-'+n1;
case 3:
return n1+'*'+n2;
case 4:
return n1+'/'+n2;
default:
return n2+'/'+n1;
}
}




}
...全文
815 19 打赏 收藏 转发到动态 举报
写回复
用AI写文章
19 条回复
切换为时间正序
请发表友善的回复…
发表回复
BCBPLC 2012-06-27
  • 打赏
  • 举报
回复
#include "stdio.h"
/*----------------------------------------*
| 24点(穷举法)算法程序 V1.0 |
| By 叶宏 2012.6.27 |
*----------------------------------------*/
bool div0=false; // 是否被0除的标志
double cal(double a,double b,int op)
{
switch (op)
{
case 0: return(a+b);
case 1: return(a-b);
case 2: return(a*b);
}
if (b==0.0)
{
div0=true; // 分母为0
return(0.0);
}
return(a/b);
}
bool isEqual(double d1,double d2) // 两个浮点数是否近似相等
{
if (div0)
{
div0=false; // 清div0
return(false);
}
double d=d1-d2;
if (d<0)
d=-d; // 求绝对值
return(d<=1e-3);
}

void __fastcall TForm1::Button4Click(TObject *Sender)
{
char op[4]={'+','-','*','/'}; // +:0 -:1 *:2 /:3

int v[4];
v[0]=Edit1->Text.ToInt(); // 输入四整数
v[1]=Edit2->Text.ToInt();
v[2]=Edit3->Text.ToInt();
v[3]=Edit4->Text.ToInt();

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)
{
int a,b,c,d; // 存放四张牌的一次排列
a=v[i1];
b=v[i2];
c=v[i3];
d=v[i4]; // 四张牌的值a,b,c,d
for (int f1=0;f1<4;f1++) // 对运算符组合
for (int f2=0;f2<4;f2++)
for (int f3=0;f3<4;f3++) // 运算符 f1,f2,f3
{
double t1,t2,t3; // 存放计算的中间值
// 第1种情况 ((a 。b)。c)。d :
div0=false;
t1=cal(a,b,f1);
t2=cal(t1,c,f2);
t3=cal(t2,d,f3);
if (isEqual(t3,24))
{
char buf[50];
sprintf(buf,"((%d%c%d)%c%d)%c%d=24",a,op[f1],b,op[f2],c,op[f3],d);
Memo1->Lines->Add(buf);
count++;
}
// 第2种情况(a 。b)。(c。 d) :
div0=false;
t1=cal(a,b,f1);
t2=cal(c,d,f3);
t3=cal(t1,t2,f2);
if (isEqual(t3,24))
{
char buf[50];
sprintf(buf,"(%d%c%d)%c(%d%c%d)=24",a,op[f1],b,op[f2],c,op[f3],d);
Memo1->Lines->Add(buf);
count++;
}
// 第3种情况 (a。(b。c))。d :
div0=false;
t1=cal(b,c,f2);
t2=cal(a,t1,f1);
t3=cal(t2,d,f3);
if (isEqual(t3,24))
{
char buf[50];
sprintf(buf,"(%d%c(%d%c%d))%c%d=24",a,op[f1],b,op[f2],c,op[f3],d);
Memo1->Lines->Add(buf);
count++;
}
// 第4种情况 a。((b。c)。d ) :
div0=false;
t1=cal(b,c,f2);
t2=cal(t1,d,f3);
t3=cal(a,t2,f1);
if (isEqual(t3,24))
{
char buf[50];
sprintf(buf,"%d%c((%d%c%d)%c%d)=24",a,op[f1],b,op[f2],c,op[f3],d);
Memo1->Lines->Add(buf);
count++;
}
// 第5种情况 a。(b。(c。d)) :
div0=false;
t1=cal(c,d,f3);
t2=cal(b,t1,f2);
t3=cal(a,t2,f1);
if (isEqual(t3,24))
{
char buf[50];
sprintf(buf,"%d%c(%d%c(%d%c%d))=24",a,op[f1],b,op[f2],c,op[f3],d);
Memo1->Lines->Add(buf);
count++;
}
}
}
if (count==0)
{
String s=String(v[0])+","+String(v[1])+","+
String(v[2])+","+String(v[3])+" 不能算出24";
Memo1->Lines->Add(s);
}
}
我的这个穷举法是用C++BUILDER6.0编的,简单易读,没有复杂高深的语句,
仅仅多重循环实现,能列出所有24点算式,速度快,花了一个晚上,
HimeTale 2008-05-31
  • 打赏
  • 举报
回复
这个还不算难,就是麻烦点.
我以前写过一个不限定只有4个数的24点算法.
胡矣 2008-05-31
  • 打赏
  • 举报
回复
呵呵
zhaoyong209 2008-05-31
  • 打赏
  • 举报
回复
一家之言,仅做参考。
这个程序实现的是根据输入N=4的情况,穷举此4个数与运算符号所有特定的组合,得到答案。但是程序所做的接收决定了输入规模只能是N=4,而不能令N的规模改变,否则会提示错误。因此,若就此程序而言,在固定了N的情况下,所使用的空间主要是double[4],double[3],double[2]和String[1024]。当然还有一些其他的变量,但无关复杂度。而这个程序的时间复杂度,在这个程序里面应该也是固定的,按原来的算法应该主要是6*6*3*6*6次{CAL1(),CAL2()和CAL()}运算。其中每次CAL*()运算都是6次四则运算。所以总共四则运算次数大约是6*6*3*6*6*(6+6+6)次。当然还有一些小规模的四则运算,但因为不可变或规模不随N变化,则忽略。
这是对此固定程序的讨论。
对于算法本身而言,N的规模自然是可变的。在原程序的基础上稍做改动,便可做出接受输入为可变长INT[N],计算其四则运算结果为固定值的所有可能。在这种情况下。算法的复杂度为下:
空间复杂度:double[N],double[N-1],double[N-2]....double[2]和String[1024]。
时间复杂度:结合我9楼对算法的理解,因为四则运算的组合恒为6,而数字取2个的组合为 Cn2=n(n-1)/2。(呵呵,不知道怎么在这个里面输入这个求概率的公式,就用Cn2或C(n-1)2代替了。凑合着看吧。)因此,仍按此算法,则需要进行的四则运算的规模约为Cn2*6*C(n-1)2*6*C(n-2)2*6....C(3)2*6*6*(6+6+6)。
由此可以看到,当N的规模增长时,算法的时间复杂度和空间复杂度都呈现线性增长。对于N较大的实例,用此算法实现将是十分耗时的,当N达到一定规模以后,可以认为此算法对此规模不可解。


对于算法的建议么,我想可以考虑使用递归法,即从结果出发反推,再来判定最后能否得到正确表达式。这样只需要在N中选一个数,所计算的次数大大减小。好处是:首先,解决了地址复杂度的问题;其次,时间复杂度锐减为N*6*(N-1)*6*(N-2)*6*....*2*6*6,把复杂度减低了一个数量级。
huhu2007 2008-05-30
  • 打赏
  • 举报
回复
zhaoyong209 厉害
favorite7w 2008-05-30
  • 打赏
  • 举报
回复
楼上在哪家工作干活呢,我去举报一下,呵呵 ^0^
zhaoyong209 2008-05-30
  • 打赏
  • 举报
回复
恭喜楼主!遇到了一个象我这样上班时间无所事事的家伙!
这个算法的核心思想是从给定的4个数中穷举所有的排列,且穷举所有可能的运算,并计算最终结果,若为24,则判断其与前面的计算结果是否完全相同,若不同,则加入到结果集合中。
算法穷举使用的方法:
先遍历这4个数中任意2个数所有可能的组合,不考虑2个数的先后次序,一共有6种可能:第1和第2,1和3,1和4,2和3,2和4,3和4。
然后再计算这2个数进行运算后所有可能得到的结果,一共也有6种可能:第1-第2,2-1,1+2,1*2,1/2,2/1。(因为+法和*法两者换序后结果相同,故只有6种可能。)
由上面2个步骤可以得出只有3个数的数组。即选中的2个数经过运算以后变成1个数,而没选中的保留。
然后在3个数的数组中按上面的思想重复操作得到只有2个数的数组。但3个数中选2个数的排列只有3种:第1和第2,1和3,2和3。但运算可能仍为6种。
最后得到只有2个数的数组,此时直接计算所有可能的值。也有6种情况。
所以方法private String[] calculate(double[] n),多次循环体的参数意义如下:
t1:4个数中选2个数对应的选法共6种
c1:运算方式共6种,此时为第1次计算
t2:3个数中选2个数对应的选法共3种
c2:运算方式共6种,控制第2次计算
c3:运算方式共6种,控制第3次计算
由此得到所有排列的结果,并将结果进行判断,添加,显示。


说完算法的思想,再说一下题外
感觉用穷举法实现过于笨拙,但不知道有没有更好的方法。
另外在此基础上,有一个小改进,改动非常小,帮助也不大,只是顺手贴上而已
原代码:
for (int t1=0; t1 <6; t1++) {
for (int c1=0; c1 <6; c1++) {
for (int t2=0; t2 <3; t2++) {
for (int c2=0; c2 <6; c2++) {
for (int c3=0; c3 <6; c3++) {
if ((c1/3==c2/3 && (c1%3)*(c2%3)!=0) ¦ ¦(c2/3==c3/3 && (c2%3)*(c3%3)!=0) ¦ ¦(c1/3==c3/3 && (c1%3)*(c3%3)!=0 && t2==2)) {
//去除连减连除的解,因为x/(y/z)=x*z/y
continue;
}
n1=cal1(n,t1,c1);
n2=cal2(n1,t2,c2);
.......
我的修改:
for (int t1=0; t1 <6; t1++) {
for (int c1=0; c1 <6; c1++) {
n1=cal1(n,t1,c1);
for (int t2=0; t2 <3; t2++) {
for (int c2=0; c2 <6; c2++) {
for (int c3=0; c3 <6; c3++) {
if ((c1/3==c2/3 && (c1%3)*(c2%3)!=0) ¦ ¦(c2/3==c3/3 && (c2%3)*(c3%3)!=0) ¦ ¦(c1/3==c3/3 && (c1%3)*(c3%3)!=0 && t2==2)) {
//去除连减连除的解,因为x/(y/z)=x*z/y
continue;
}
n2=cal2(n1,t2,c2);
........
修改的理由:
可以减少(6*6*3*6*6-6*6*3)次cal1(n,t1,c1)运算。
tottimax 2008-05-30
  • 打赏
  • 举报
回复
各位高手能不能从程序的整体的时间、空间复杂度说说?各种改进意见也可以说说。这对我很重要!先谢各位热心人了,尤其是zhaoyong209
aipb2008 2008-05-30
  • 打赏
  • 举报
回复
贴代码用代码格式嘛,这样谁会看啊~
goosman 2008-05-30
  • 打赏
  • 举报
回复
关注下
cuijie_cn 2008-05-30
  • 打赏
  • 举报
回复
太麻烦了 :)
莫情莫钱 2008-05-30
  • 打赏
  • 举报
回复
回头看看
zhouzongxi 2008-05-30
  • 打赏
  • 举报
回复
确实,比较长!帮你顶
apples_009 2008-05-29
  • 打赏
  • 举报
回复
太长啦..如果用swing来做就不错
grellen 2008-05-29
  • 打赏
  • 举报
回复
mark
loujianchengdd 2008-05-29
  • 打赏
  • 举报
回复
哈,帮顶吧,这个估计没人会仔细看,本来工作一天就头疼,哈
goodmrning 2008-05-29
  • 打赏
  • 举报
回复
看不懂啊,等高手.帮你顶一个?
jiaping108 2008-05-29
  • 打赏
  • 举报
回复
晕,看到长代码就晕~~
tottimax 2008-05-29
  • 打赏
  • 举报
回复
这是一个计算24点的算法,即任意4个1到13的整数,通过加减乘除四则运算计算出24,可以使用括号。分析可以说说算法的时间、空间复杂度,以及算法对表达式优先级的处理。越详细越好!

62,610

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧