24点_target

  这是一个众所周知的小游戏,玩法我想大家都知道,小时候,我们通常是这样玩,两个人,每人手里一摞牌,两人分别抽出两张,共4张牌,用这4张牌计算24点,如果无法算出24点的牌,要各自收回,否则,先算出24点的人报出24点的算法并让对方收下这4张牌。这样,计算反应慢点的人往往手里的牌会越来越多。

  大二的时候,记得有同学问我1,3,4,6,怎么计算24点,冥思苦想了半天,不得其解,那个时候并没想到去网络上找找答案,只是想着编个程序试试能否找到答案。说到互联网。不可否认,网络让我们处在一个知识爆炸的年代,同时也让我们变得慵懒浮躁,我们开始倾向于从百度窗口中找到你想要的答案,而不愿意通过思考去获取问题的答案。没有比通过自己思考和努力获得问题的答案最使人欢欣鼓舞,比如我大学最喜欢的游戏,暗黑2,从1级开始一级一级的练,打完普通模式打地狱模式,经常在一个又一个打怪升级的任务中转晕了脑袋,其中的乐趣来自不断的升级,技能提升,爆装备和任务的完成。自从下载了网上的变态存档后,只要一现身,放佛启用了霸王色霸气,周边小怪物直接倒下,连最终boss巴赫都弱爆了,那时候,突然感觉不再爱了,至今未再碰暗黑2,哪一天老了,想再怀旧一下或许会再打开试试。

  言归正传,如何用计算机去计算结果呢,我希望用比较简洁的方式去解决

    假设有四张牌 A ,B ,C, D ,不管怎么计算,总有两张牌先计算,我们用排列组合的方式取两张牌计算,把计算的结果看做一张牌,那么手里还有三张牌,对这三张牌再次进行排列组合取两张牌计算,最后,将剩下的两张牌进行计算,看看能否取得最终结果24点

    由4张牌3运算操作可以得到,整个运算过程全部枚举的话,需要7重循环,代码会相当臃肿,这里暂时先用递归来代替。

   首先枚举出一个牌的排列,然后对这个排列,枚举出所有运算操作的排列,以此运算,看能够得到目标结果。

   对于每一个牌的排列,假设为A,B,C,D,它的运算无非有如下几种情形

   (A※B)※(C※D)

   ((A※B)※C)※D

  (A※(B※C))※D

   A※((B※C)※D)

   A※(B※(C※D))

其中,,※表示任意运算符号,代码如下

#include <string.h>
#include <math.h>

#define MAX_CARDS     10
#define CARDS_NUM     4
#define OPT_NUM       4
#define MAX_CAL       24
#define MAX_DEL       0.01

int p_array[MAX_CARDS] = {1,2,3,4};
int used_array[MAX_CARDS]={0};
char opt_smobol[OPT_NUM+1] = {"+-*/"};
int opt_array[CARDS_NUM-1] = {0};
int input[CARDS_NUM] ={1,3,4,6};  //待计算的牌
const char dbg_string[5][26] ={
"(%d %c%d)%c(%d%c%d)",
"((%d%c%d)%c%d)%c%d",
"(%d%c(%d%c%d))%c%d",
"%d%c((%d%c%d)%c%d)",
"%d%c(%d%c(%d%c%d))"};
/*计算A,B,根据opt运算*/

double cal(double a,double b,int opt)
{
    switch(opt)
    {
    case 0: return a+b;
    case 1:return a-b;
    case 2:return a*b;
    case 3:
        return b==0.0?0:a/b;
    }
    return 0;              
}

/*检测是否为24,由于由除法运算,必须允许部分误差*/
bool check_24(double rst)
{
    rst -= 24.0;
    rst =fabs(rst);
    if(rst<MAX_DEL) return true;
    return false;
}

/*检验结果并输出运算过程*/
void check_rst(int *array,int *opt_array,double *rst_array)
{
    for(int i=0;i<5;i++)
    {
        if(check_24(rst_array[i]))
        {
            printf(dbg_string[i],input[array[0]-1],opt_smobol[opt_array[0]],
                              input[array[1]-1],opt_smobol[opt_array[1]],
                              input[array[2]-1],opt_smobol[opt_array[2]],
                              input[array[3]-1]
                              );
            printf("\n");
        }
    }

}
/*输入一个牌的排列,计算结果
   (A※B)※(C※D)

   ((A※B)※C)※D

  (A※(B※C))※D

   A※((B※C)※D)

   A※(B※(C※D))
*/
void cal_result(int *array,int *opt_array,int idx)
{
      int i;
      int A,B,C,D;  
      double A_B;
      double B_C;
      double C_D;
      double rst[5]; //五种运算的不同结果存储在数组中

      if(idx==(CARDS_NUM-1))
      {
         A = input[array[0]-1];
         B = input[array[1]-1];
         C = input[array[2]-1];
         D = input[array[3]-1];
         A_B = cal(A,B,opt_array[0]);
         B_C = cal(B,C,opt_array[1]);
         C_D = cal(C,D,opt_array[2]);
         rst[0] = cal(A_B,C_D,opt_array[1]);
         rst[1] = cal(cal(A_B,C,opt_array[1]),D,opt_array[2]);
         rst[2] =cal(cal(A,B_C,opt_array[0]),D,opt_array[2]);
         rst[3] =cal(A,cal(B_C,D,opt_array[2]),opt_array[0]);
         rst[4] =cal(A,cal(B,C_D,opt_array[1]),opt_array[0]);
         check_rst(array,opt_array,rst);
         return;
      }
      for(i=0;i<OPT_NUM;i++)
      {
          opt_array[idx] = i;
          cal_result(array,opt_array,idx+1);       
      }
}

/*取得所有运算排列并计算结果*/
void getcardarray(int *p_array,int idx)
{
    int j;
    if(idx>=CARDS_NUM) 
    {
        cal_result(p_array,opt_array,0);
        return;
    }
    for(j=0;j<CARDS_NUM;j++)
    {
        if(used_array[j]==0)  //未被占用
        {
            used_array[j]=1;
            p_array[idx] = (j+1);
            getcardarray(p_array,idx+1);
            used_array[j]=0;
        }
    }
}

int main()
{
    getcardarray(p_array,0);
    return 0;
}

运行结果如图

24点_border_02

换一组数据,1,5,5,5

运行结果如下

24点_border_03

由于存在重复的数字,其实以上的排列只有4个,并且以上输出结果其实都是重复的,那么如何规避这些重复的结果呢,涉及到归一化的过程,后面还会再提到。值得注意的是,由于除法运算中的损失,结果未必为24.0,有些误差,在判断结果的时候,必须允许存在一定的误差才行

下一内容提示 五子棋