马勇波  陈欣庆
(解放军理工大学工程兵工程学院研究生二队,南京 210007)
 
    摘  要  文章通过一个二维数组定义麻将的数据结构,并在此基础上设计了一种判断麻将是否胡牌的算法,该算法主要步骤的时间复杂度为O (n ),且基本上处于“原地工作”。在经过算法判断运算后,该二维数组最终会恢复到最初的数据。
    关键词  麻将;胡牌算法;复杂度

1 引言
    麻将起源于中国,它集益智性、趣味性、博弈性于一体,是中国传统文化的一个重要组成部分。麻将胡牌的形式千变万化,数据结构的定义也不尽相同,相应的胡牌的算法也多种多样,很值得程序设计人员学习探讨。下面介绍了一种胡牌的算法,并讨论了它的复杂度。
2  数据结构的定义
    麻将由“万”、“筒”、“索”、“字”四类牌组成,其中“万”又分为“一万”“二万”……“九万”各4张共36张,“筒”“索”类似,“字”分为“东”“南”“西”“北”“中”“发”“白”各4张共28张。这里定义了一个4 x 10的数组int allPai [4][10],它记录着手中的牌的全部信息,行号记录类别信息,第0~3行分别代表“万”“筒”“索”“字”。以第0行为例,它的第0列记录了牌中所有“万”的总数,第1~9列分别对应着“一万”~“九万”的个数,“筒”“索”类似。“字”不同的是第1~7列对应的是“东”“南”“西”“北”“中”“发”“白”的个数,第8,9列恒为0。根据麻将的规则,数组中的牌总数一定为3n+2,其中n=0,1,2,3,4。如有下面的数组:
    allPai[4][10]={{6,1,1,1,0,3},{5,0,2,0,3},{0},{3,0,3}}
    它表示手中的牌为:“一万”“二万”“三万”“五万”“五万”“五万”“二筒”“二筒”“四筒”“四筒”“四筒”“南”“南”“南”,共6张“万”,5张“筒”,0张“索”,3张“字”。
3  算法设计
    由于“七对子”“十三幺”这种特殊的牌型胡牌的依据不是牌的相互组合,而且规则也不尽相同,这里将这类情况排除在外。
    尽管能构成胡的牌的形式千变万化,但稍加分析可以看出它离不开一个模型:它可以分解为“三、三……三、二”的形式(总牌数为3n+2张),其中的“三”表示的是“顺”或“刻”(连续三张牌叫做“顺”,如“三筒”“四筒”“五筒”,“字”牌不存在“顺”;三张同样的牌叫做“刻”,如“三筒”“三筒”“三筒”);其中的“二”表示的是“将”(两张相同的牌可作为“将”,如“三筒”“三筒”)。
    在代码实现中,首先就判断手中的牌是否符合这个模型,这样就用极少的代价排除了大多数情况,具体作法是用3除allPai [i][0],其中i = 0, 1, 2, 3,只有在余数有且仅有一个为2,其余全为0的情况下才可能构成胡牌。对于余数为0的牌,它一定要能分解成一个“刻”和“顺”的组合,这是一个递归的过程,由函数bool Analyze(int [],bool)处理;对于余数为2的牌,一定要能分解成一对“将”与“刻”和“顺”的组合,由于任何数目大于等于2的牌均有作为“将”的可能,需要对每张牌进行轮询,如果它的数目大于等于2,去掉这对“将”后再分析它能否分解为“刻”和“顺”的组合,这个过程的开销相对较大,放在了程序的最后进行处理。在递归和轮询过程中,尽管每次去掉了某些牌,但最终都会再次将这些牌加上,使得数组中的数据保持不变。
    最后分析递归函数bool Analyze(int [], bool),数组参数表示一类牌:“万”、“筒”、“索”、“字”之一,布尔参数指示数组参数是否是“字”牌,这是因为“字”牌只能“刻”而不能“顺”。对于数组中的第一张牌,要构成胡牌它就必须与其它牌构成“顺”或“刻”。如果数目大于等于3,那么它们一定是以“刻”的形式组合。譬如:当前有3张“五万”,如果它们不构成“刻”,则必须有3张“六万”3张“七万”与其构成3个“顺”(注意此时“五万”是数组中的第一张牌),否则就会剩下“五万”不能组合,而此时的3个“顺”实际上也是三个“刻”。去掉这三张牌,递归调用bool Analyze(int [],bool)函数,成功则胡牌。当该牌不是字牌且它的下两张牌均存在时它还可以构成“顺”,去掉这三张牌,递归调用bool Analyze (int [],bool)函数,成功则胡牌。如果此时还不能构成胡牌,说明该牌不能与其它牌顺利组合,传入的参数不能分解为“顺”和“刻”的组合,不可以构成胡牌。
4  时间复杂度分析
    度量一个算法的效率,通常采用事前分析估算的方法。由于语言、编译程序等的不同,用绝对的时间单位来比较并不合适,一般撇开这些因素,用时间复杂度T (n)=O (f (n))来衡量,它表示随问题规模n的增大,算法执行时间的增长率和f (n)的增长率相同,算法的工作量只与问题的规模相关[1]。
    从胡牌算法可以看出,在许多情况下,函数能很快返回成功或者失败,而在某些情况下,函数可能需要不断地递归。虽然从理论上说每张牌出现的概率是相等,但要计算出它的平均时间复杂度仍然是相当困难的,这里采用另外一种思路:讨论最坏情况下的复杂度。
    不难看出,算法的主要开销集中在分解为“刻”“顺”组合的递归和寻找“将”的轮询上,而实际上轮询的复杂性也在于它调用了递归函数,因此这里主要分析递归函数的复杂度。
可以这样认为,当递归函数bool Analyze(int [],bool)传入的数组参数中有3n张牌时,问题规模为n。在j从1到10的循环中,没有递归操作,在不限定n<=4的情况下它的开销与其它处明显不在一个数量级,这里假定它消耗了10个时间单位。从代码容易看出组合成“刻”要比组合成“顺”的代价要小,所以最坏的情况下,没有“字”牌,没有数目大于等于3的牌,它们最终都以“顺”组合。记问题规模为n的该递归函数的复杂度为f (n),则在最坏的情况下:
f (n) = 1+10+4+f (n-1) +4
        = f (n-1) +19
     于是f (n) - (f (n-1) = 19
    ……
f (1) - f (0) = 19
    又  f (0) = 1
所有等式两边分别相加,
    得f (n) = 19n+1
    如果不限定问题规模n<=4,可以认为该递归函数的时间复杂度为O (n )。
5  空间复杂度分析
    空间复杂度是对算法所需存储空间的量度。记作T (n)=O (f (n))。发生函数调用时,系统首先要保存三方面的信息:(1)当前执行函数的中断返回地址;(2)当前执行函数调用时与形参结合的实参值,包括函数名和函数参数;(3)当前执行函数的局部变量,递归函数也是如此[1]。
对于此胡牌算法,一方面,递归函数调用自身的次数与问题规模n成正比,当n增大时,所需要的用来存储中断返回地址、参数、局部变量等信息的内存单元也越多。另一方面,数组作为参数传递的只是指针,每次数组操作都在一个存储空间内进行,与问题规模n没有关系,从某种意义上可以说它是在“原地工作”。
6  源代码
#include <stdio.h>
#include <math.h>
//函数声明
bool Win(int [4][10]);
bool Analyze(int [],bool);
 
int main(int argc, char* argv[])
{
     //定义手中的牌
     int allPai[4][10]={
                       {6,1,4,1},//万
                       {3,1,1,1},//筒
                       {0},//索
                       {5,2,3}//字
                       };
     if (Win (allPai))
         printf("Hu!/n");
     else
         printf("Not Hu!/n");
     return 0;
}
//判断是否胡牌的函数
bool Win (int allPai[4][10])
{
     int jiangPos;//“将”的位置
int yuShu;//余数
     bool jiangExisted=false;
     //是否满足3,3,3,3,2模型
     for(int i=0;i<4;i++)
     {
         yuShu=(int)fmod(allPai[i][0],3);
         if (yuShu==1)
         {
              return false;
         }
         if (yuShu==2) {
              if (jiangExisted)
              {
                   return false;
              }
              jiangPos=i;
              jiangExisted=true;
         }
     }
     for(i=0;i<4;i++)
     {
         if (i!=jiangPos) {
              if (!Analyze(allPai[i],i==3))
              {
                   return false;
              }
         }
     }
     //该类牌中要包含将,因为要对将进行轮询,效率较低,放在最后
     bool success=false;//指示除掉“将”后能否通过
     for(int j=1;j<10;j++)//对列进行操作,用j表示
     {
         if (allPai[jiangPos][j]>=2)
         {
              //除去这2张将牌
              allPai[jiangPos][j]-=2;
              allPai[jiangPos][0]-=2;
         if(Analyze(allPai[jiangPos],jiangPos==3))
              {
                   success=true;
              }
              //还原这2张将牌
              allPai[jiangPos][j]+=2;
              allPai[jiangPos][0]+=2;
              if (success) break;
         }
     }
     return success;
}
//分解成“刻”“顺”组合
bool Analyze(int aKindPai[],bool ziPai)
{
     if (aKindPai[0]==0)
     {
         return true;
     }
//寻找第一张牌
     for(int j=1;j<10;j++)
     {
         if (aKindPai[j]!=0)
          {
              break;
         }
     }
     bool result;
     if (aKindPai[j]>=3)//作为刻牌
     {
         //除去这3张刻牌
         aKindPai[j]-=3;
         aKindPai[0]-=3;
         result=Analyze(aKindPai,ziPai);
         //还原这3张刻牌
         aKindPai[j]+=3;
         aKindPai[0]+=3;
         return result;
     }
     //作为顺牌
     if ((!ziPai)&&(j<8)
         &&(aKindPai[j+1]>0)
         &&(aKindPai[j+2]>0))
     {
         //除去这3张顺牌
         aKindPai[j]--;
         aKindPai[j+1]--;
         aKindPai[j+2]--;
         aKindPai[0]-=3;
         result=Analyze(aKindPai,ziPai);
         //还原这3张顺牌
         aKindPai[j]++;
         aKindPai[j+1]++;
         aKindPai[j+2]++;
         aKindPai[0]+=3;
         return result;
     }
     return false;
}
 
程序的运行结果为:Hu!
参考文献
[1] 严蔚敏,吴伟民.数据结构[M].北京:清华大学出版杜,1992年6月第二版 12-15.

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/GameDO/archive/2006/10/18/1340102.aspx

麻将胡牌算法的一种设计及其分析相关推荐

  1. 麻将胡牌算法带癞子 python实现

    姐姐:你去帮我和闺蜜打麻将? 学霸哥哥:可是我不会打麻将呀! 姐姐:你不是学霸吗?我教你一个麻将公式,我闺蜜可是单身哟! 学霸哥哥:什么公式? 姐姐:麻将胡牌公式: AAA*M+ABC*N+BB,WM ...

  2. 麻将胡牌算法——C#

    这里只介绍普通的麻将胡牌算法,也就是7个对子或者 1个对子+3*N; N = 三个顺子或者三个一样的 ,其中字牌(东南西北中发白)不能算顺子. 首先对于每张牌 它有牌的的数字 1-9,牌的种类 (万条 ...

  3. 麻将胡牌算法(遍历+剪枝)

    麻将胡牌算法(遍历+剪枝) 简介 麻将胡牌算法及代码 1. 方法引入 2. 类型定义 2.1 牌定义 2.2 牌特征定义 3. 计算胡牌 3.1 检测十三幺牌型 3.2 检测七小对牌型 3.3 检测普 ...

  4. 可带癞子的通用麻将胡牌算法

    本文原创文章,转载注明出处,博客地址 https://segmentfault.com/u/to... 第一时间看后续精彩文章.觉得好的话,顺手分享到朋友圈吧,感谢支持. 笔者前段时间做过一款地方麻将 ...

  5. 麻将胡牌算法 极速(速度接近理论极限)

    此麻将胡牌算法优点: 1.可处理多赖子牌(万能牌) 2.算法速度极快:1ms可大约计算1W+副手牌是否可胡(带赖子.0.08us左右),不带赖子的牌型更快.(最新版的算法速度感觉已很接近理论极限值) ...

  6. 麻将 胡牌 算法(任意癞子)

    分享一个麻将胡牌算法,支持多癞子,自己对麻将胡牌的理解写的一套快速识别胡牌逻辑,核心逻辑500行代码,仅对同条万进行处理,字花牌不包含在内,易理解,1M次随机胡牌牌型大概3秒左右.原创分享,我的算法也 ...

  7. Unity3D 通用麻将胡牌算法

    https://blog.csdn.net/qq_38064109/article/details/78933589 正常的麻将胡牌方式为满足N * ABC + M *DDD +EE 的形式,及存在一 ...

  8. 带赖子的麻将胡牌算法Java_有人讨论下麻将胡牌,出牌算法吗,求思路

    前段时间学会了打麻将,觉得老祖宗的智慧真的博大精深,很好玩,食胡的时候真兴奋啊,于是空余时间就想自己写个麻将游戏出来,模仿欢乐麻将那种,数学差,想了两个礼拜才想出一个胡牌算法,前段时间学会了打麻将. ...

  9. 癞子麻将胡牌算法实现

    最先实现的就是算法的实现. 需求:碰杠胡  ,不能吃 ,不能听 ,仅仅能自摸胡,当中癞子能够做随意牌可是不能碰和杠. 写的时候还不会玩麻将,还是老板教的.^_^ 最麻烦的是胡牌算法.之前搜到的都是不包 ...

最新文章

  1. js原生选项卡(自动播放无缝滚动轮播图)二
  2. 通过Katalon Automation Recorder 3步实现自动化测试
  3. [ARM异常]-同步异常产生和返回(svc/hyc/smc/eret)
  4. Python中关于XML-RPC原理
  5. python 保存网页到印象笔记_如何将网页内容保存到印象笔记?
  6. Aptana 添加jQuery提示
  7. MySQL 基本信息的查询(初始化配置信息 my.ini)
  8. The best programmers are the quickest to Google
  9. 去除电脑端QQ退格到头的音效
  10. python贝叶斯分析方法实例_python 贝叶斯分析对应的代码
  11. 国有资产综合管理平台需求分析
  12. postman文件导入
  13. 操作系统漏洞检测与利用
  14. 高级Java程序员必备:《IDEA问题库》常见问题及解决方案,提升开发效率3(JAVA 小虚竹)
  15. 服务器装系统报0x0000005d,安装Win8系统出现error code 0x0000005d如何解决?
  16. 关于php的广告语,解说词、广告词、欢迎词专题训练a href=http://ruiwen.com/friend/list.php(教师中心专稿)/a...
  17. 手机b站封面提取网站_二次元之家 视频网站B站上海与北京办公设计欣赏
  18. 基于AM437x的FPGA与ARM通信测试
  19. 如何使用Excel表格精准分析PT100温度阻值关系?
  20. 阿里二面:设计一个电商平台积分兑换系统!

热门文章

  1. 超全面!UI设计中的五大主要视觉元素
  2. 【软工项目组】第三次会议(调研结果)
  3. 电路设计篇【5】MOS管驱动电路设计,如何让MOS管快速开启和关闭?
  4. COM编程入门 第一部分——什么是COM,如何使用COM
  5. python从入门到实践电子版-Python编程从入门到实践PDF电子书免费下载
  6. sqli-labs————less 22
  7. 《好想好想谈恋爱》插曲整理
  8. 每日英语阅读(二十一)
  9. javax.mail.MessagingException: Could not connect to SMTP host
  10. 计算机硬件技术的应用毕业论文,计算机硬件技术论文