带百搭的麻将胡牌判断算法

以前我写了一个判断麻将是否胡牌的算法,不过不支持百搭。最近有一个朋友问我,如何有百搭,算法如何写。我写了一个,贴出来,让网友看看。

算法输入: 整数数组 a[0..n-1]表示一手牌,其中,n 是牌的张数,比如 14。 

                牌的编码可自定,比如: 101-109 表示一万到九万,

                                                201-209表示一条到九条,

                                                301-309表示一筒到九筒,

                                                411,421,431,441,451,461,471表示东南西北中发白,

                                               500表示百搭。

算法预处理:

若 n 模 3不等于 2,直接输出:牌数不对,是相公,算法结束。否则:

把百搭删除,把剩下的普通牌进行排序:

    int i,m;

    m=0;

   for (i=0;i

   if (a[i]不是百搭) a[m++]=a[i];   // m 就是普通牌的张数

  把 a 中前 m 个元素进行排序;

随后,我把 a 看成左、中、右三段,其中,左侧段表示“成牌区”,即:它们由刻、顺组成;

中段表示试探区,算法要重点处理它们;而右侧段则是“杂牌”区,即:它们由非刻非顺组成。

在算法预处理后,显然,a 的左段长度为0,中段长度是m,右段长度是0,调用下面的“理牌”算法:

LiPai(a,0,m,0,n-m);

其中,n-m是指百搭的张数。

理牌算法:

void LiPai(int a[],int LeftCount,int MidCount,int RightCount,int CountOfBaiDa)

{

    if (MidCount<3)                   // 试探区已不足 3 张,理牌过程结束,进入“理杂牌”阶段

   {

      调用测试算法;   // 见后文

   }

   else                          // 试探区至少 3 张,可以试着从中取出刻子和顺子

   {

     int * p=&a[LeftCount];   // 让 p 指向试探区首张

     int x=p[0];                // 取出试探区首张

     if (p[1]==x && p[2]==x)             // 发现一个刻子

    {

         LiPai(a,LeftCount+3,MidCount-3,RightCount,CountOfBaiDa);  // 把刻子放到成牌区,递归地调用理牌算法

    }

   在 p[0],p[1],...p[MidCount-1] 中寻找 x+1 和 x+2;

   if (找到)

   {

      把 x,x+1,x+2 放入 p[0],p[1],p[2];

     把剩下的牌放入 p[3],p[4],...,p[MidCount-1]中;

      LiPai(a,LeftCount+3,MidCount-3,RightCount,CountOfBaiDa);  // 把顺子放到成牌区,递归地调用理牌算法

    对p[0],p[1],...,p[MidCount-1] 排序; // 恢复原样

   }

   让 p[0],p[1],...p[MidCount-1] 循环左移一次; // 这样,X 就成为杂牌区左边的元素了

    LiPai(a,LeftCount,MidCount-1,RightCount+1,CountOfBaiDa);  // 把x放入杂牌区,递归地调用理牌算法

   让 p[0],p[1],...p[MidCount-1] 循环右移一次; // 这样,X 又回到试探区最左侧了

}

 

上述递归算法的终止条件是 MidCount<3,当该条件满足时,调用下面算法(即上文提到的测试算法)

  杂牌总张数=MidCount+RightCount;    // 不足 2 张的中段,实际上也是杂牌

  if (CountOfBaiDa==0)                     // 如果没有百搭

  {

     if (杂牌总张数==2 && 两张杂牌相同)   // 杂牌只能是一个对子,它将是麻将头

    {

       输出一个胡牌方案:刻、顺是 a[0],a[1],...,a[LeftCount-1],麻将头(对子)是 剩下的两张杂牌;

   }

 }

 else                                      // 如果有百搭, 让一张百搭配2张杂牌

 {

    if (杂牌总张数-2*CountOfBaiDa<=2)   // 配完之后,剩下的牌数若不超过 2,则有希望胡牌,需要进一步探测

    {

       申请数组 b[];

      把 a[LeftCount],a[LeftCount+1],...,a[m-1] 放入 b[0],b[1],...,b[MidCount+RightCount-1] 中;

        把b[]排序;

       LiZaPai(a,LeftCount,b,0,MidCount+RightCount,0,CountOfBaiDa);   // 总杂牌区也被划分成 3 段:左段 中段 右段,见下文

      释放b[];

    }

 }

 

“理杂牌”LiZaPai(...) 算法,与普通理牌算法类似,不过,它的目标是理出对子或搭子。它也把待理区划分成 3 段:

左段:成对/搭区

中段:待测区

右段:杂牌区

void LiZaPai(int a[],int OKCount,int b[],int LeftCount,int MidCount,int RightCount,int CountOfBaiDa)  // OKCount 是刻子/顺子的张数,在 a 的最左侧

{

    if (MidCount<2)               // 待测区不足 2 张,

   {

     调用试配算法;   // 见下文

   }

  else

  {

    int * p=&b[LeftCount];   // 让 p 指向试探区;

    int x=p[0];     // 取出首张

    if (x==p[1])   // 找到一个对子

   {

      LiZaPai(a,OKCount,b,LeftCount+2,MidCount-2,RightCount,CountOfBaiDa); // 递归地求解

   }

   在 p[1],p[2],...p[MidCount-1] 中寻找 x+1;

   if (找到)

   {

     把 x,x+1 放入 p[0],p[1];

    把剩下的牌放入 p[2],p[3],...,p[MidCount-1];

      LiZaPai(a,OKCount,b,LeftCount+2,MidCount-2,RightCount,CountOfBaiDa); // 递归地求解

     把 p[0],p[1],...,p[MidCount-1] 排序;

   }

   在 p[1],p[2],...p[MidCount-1] 中寻找 x+2;

   if (找到)

   {

     把 x,x+2 放入 p[0],p[1];

    把剩下的牌放入 p[2],p[3],...,p[MidCount-1];

      LiZaPai(a,OKCount,b,LeftCount+2,MidCount-2,RightCount,CountOfBaiDa); // 递归地求解

     把 p[0],p[1],...,p[MidCount-1] 排序;

   }

   让 p[0],p[1]....,p[MidCount-1] 循环左移;   // x 称到杂牌区;

      LiZaPai(a,OKCount,b,LeftCount,MidCount-1,RightCount+1,CountOfBaiDa); // 递归地求解

   让 p[0],p[1]....,p[MidCount-1] 循环右移;   // x 回到首位

  }

}

 

试配算法如下:

   杂牌总数=MidCount+RightCount;   // 此时,试探区成了杂牌区

   if (杂牌总数==0)                              // 全是对子/搭子

  {

      for (i=0;i

     if (b[i]==b[i+1])                         // 找到一个对子, 它是麻将头

     {

            if (LeftCount/2-1<=CountOfBaiDa)   // 剩下的对子搭子必须全部由百搭配成刻/顺

           {

                输出一个胡牌方案:刻/顺是 a[0],a[1],...,a[OKCount-1],以及 b[0]到b[LeftCount-1] 中除去 i,i+1 两元素后, 剩下的对/搭与百搭配成的刻/顺,以及剩下的百搭组成的刻子;

                                            麻将头是是 b[i],b[i+1];

           }

     }

                                              // 现在,必须用两个百搭配成麻将头, 所有搭子要由剩下的百搭配成刻/顺

    if (CountOfBaiDa>=2 && LeftCount/2<=CountOfBaiDa-2)

    {

         输出一个胡牌方案:刻/顺是 a[0],a[1],...,a[OKCount-1],以及 b[0]到b[LeftCount-1] 每个对/搭与百搭配成的刻/顺,以及剩下的百搭组成的刻子;

                                    麻将头是百搭,百搭;

    }

  }

  else

  if (杂牌总数==1)                      // 只有一张杂牌, 此时,必须用一个百搭与此杂牌配成麻将头

  {

      if (CountOfBaiDa>=1 &&  LeftCount/2<=CountOfBaiDa-1)  // 用一个百搭与杂牌配成对子,而对子/搭子的副数不能比剩下的百搭数多, 这样就可以胡牌

      {

          输出一个胡牌方案:刻子/顺子是 a[0],a[1],...,a[OKCount-1],以及 b[0],b[1],百搭,b[2],b[3],百搭,...b[LeftCount-2],b[LeftCount-1],百搭,以及

                                      配完对子、刻/顺后剩下的百搭组成的刻子;

                                       麻将头是:杂牌,百搭。

      }

  }

  else                             // 有2张或以上杂牌,不胡,因为,最多只能用一个百搭+一张杂牌形成麻将头

  {

  }

 

上面算法我用 VC++ 实现,运算速度很快,一般耗时 0.04368ms。我的 CPU:2.3GHz ,i7

你可能感兴趣的:(算法,带百搭的麻将胡牌判断算法)