24点游戏算法

游戏规则: 一副牌中去掉大小王,在剩下的52张牌中任意取四张。使用四则运算,使其最后结果为24.其中每张牌只能使用一次且A=1,J=11,Q=12,K=13。

     譬如 2,4,6,8  ------>  结果为 6/(4-2)*8=24;

算法思考: 首先,从宏观上说,这种问题都是遍历穷举。再看看运算符,其中+,* 都是没有顺序的。即(a*b=b*a), 但是 -、/ 是有顺序的。那么假设都有顺序的。那么就可以统一处理了(最多效率低点,先解决问题。再考虑优化)。那么遍历所有a,b,c,d 以及 三次运算 。即A(4,4) *4*4*4 就是该算法的复杂度。(事实证明我错了。后面会讲到。)

     微观上,由于中间有除法,那么不能用int类型来储存数据了。需要用double或者float.每次运算都只有两个数运算。我们可用 Function CalcValue(float x,float y , char sy)来表示。x表示第一数,y表示第二个数, sy表示四则运算符之一。

  代码如下:

View Code
        public float CalcValue(float x, float y, char f)
{
float res = 0;
switch (f)
{
case '+': res = x + y;
break;
case '-': res = x - y;
break;
case '*': res = x * y;
break;
case '/': res = x / y;
break;
default:
break;
}
return res;
}

  遍历代码如下

View Code
for (int i1 = 0; i1 < Nums.Count; i1++)//Nums 为4个数   
{
List<float> Nums1 = CloneValue(this.Nums);
float num1 = Nums1[i1];//第一个数
Nums1.RemoveAt(i1);//移除第一数

for (int i2 = 0; i2 < Nums1.Count; i2++)//遍历剩下的3个数
{
List<float> Nums2 = CloneValue(Nums1);
float num2 = Nums1[i2];//第二个数
Nums2.RemoveAt(i2);

for (int i3 = 0; i3 < Nums2.Count; i3++)
{
List<float> Nums3 = CloneValue(Nums2);
float num3 = Nums2[i3];//第三个数
Nums3.RemoveAt(i3);

//第四个数,因为最后一个了。不要循环了。
float num4 = Nums3.First();

//即四个数的顺序为num4,num3,num2,num1;接下来遍历符号

for (int sy1 = 0; sy1 < 4; sy1++)//遍历第一的运算符号
{
char currentSymbol1 = symbols[sy1];
float tem1 = CalcValue(num4, num3, currentSymbol1);//第一个中间值

for (int sy2 = 0; sy2 < 4; sy2++)
{
char currentSymbol2 = symbols[sy2];

float tem2 = 0;
for (int dir = 0; dir < 2; dir++)//一开始没写这个循环。只计算了下面注释的部分。其实写程序到这里我就感觉不太对了。。后来验证果然不对。
{
if (dir == 0)
{
tem2 = CalcValue(tem1, num2, currentSymbol2);
}
if (dir == 1)
{
tem2 = CalcValue(num2, tem1, currentSymbol2);
}
// float tem2 = CalcValue(tem1, num2, currentSymbol2);
for (int sy3 = 0; sy3 < 4; sy3++)
{
char currentSymbol3 = symbols[sy3];
float tem3 = 0;
for (int dir2 = 0; dir2 < 2; dir2++)//同样。这个一开始没写。后来补的。
{
if (dir2 == 0)
{
tem3 = CalcValue(tem2, num1, currentSymbol3);
}
if (dir2 == 1)
{
tem3 = CalcValue(num1, tem2, currentSymbol3);
}
// float tem3 = CalcValue(tem2, num1, currentSymbol3);

if (tem3 == 24)//发现了计算
{
//下面的都是后来补的。所以导致程序很乱。
if (dir == 0 && dir2==0)//按顺序的
{
Console.WriteLine("{0}{1}{2}, {3}{4},{5}{6}", num4, currentSymbol1, num3, currentSymbol2, num2, currentSymbol3, num1);
}
if (dir == 1 && dir2==0)//第二次计算时 num2与temp四则运算 如 6/(4-2) * 8
{
Console.WriteLine("{3}{4},{0}{1}{2},{5}{6}", num4, currentSymbol1, num3, num2, currentSymbol2, currentSymbol3, num1);
}
if (dir==0 && dir2 == 1)// 最后次计算 num4与temp四则运算 如 4/(1-5/6)
{
Console.WriteLine("{6}{5}, ({0}{1}{2},{4}{3})", num4, currentSymbol1, num3, num2, currentSymbol2, currentSymbol3, num1);
}
if (dir==1 && dir2 == 1)// 如 8/(3-8/3)
{
Console.WriteLine("{6}{5}, {4}{3},{0}{1}{2}", num4, currentSymbol1, num3, num2, currentSymbol2, currentSymbol3, num1);
}
return;
}
}//---

}

}//---
}
}
}
}



}

  从遍历代码中发现。我们少考虑的点东西。 即并不是遍历所有abcd以及 运算的组合就行了。我们遍历的只是数出现的顺序。而并没有遍历所有表达式。

举个例子: ((a+b)-c)+d 和 (c-(a+b))+d  这两个表达式 都是 ab运算, 再和c运算,最后和d运算。 运算数的顺序是一样的,都是abcd,但是表达式不同。

也就是说,遍历数所有abcd的组合还不够。还需要添加一个顺序。计算的顺序。于是在遍历的程序中 多了两个for(dir=0;dir<2;dir++) 的循环。。。

重新思考:  看到计算表达式 ((a+b)-c)+d  ,就想起大学时候的波兰表达式。 先去括号再说。ab+c-d+。很好很强大!!这个就能解决顺序的问题。不必先弄所有的数字的组合(带顺序的,A(x,x),不是C(x,x) ) 然后还要考虑 计算的顺序。。。。那么关键是有多少种符合的表达式类型呢? 穷举!!!

改天再想吧。。。

罗列下待解决的问题: 效率问题。太多的重复判断。或许波兰表达式能解决。

           去括号后相同的表达式。。 譬如 a-(b+c)+d  与 a-b-c+d  是相同的。




原文地址:https://www.cnblogs.com/xinjian/p/2221544.html