麻将胡牌判定的判定算法

麻将胡牌判定的判定算法

问题背景 :
简化了麻将规则,给定[1s-9s],[1b-9b],[1t-9t]一共27种牌,每种牌都有4张。需要判断给定的牌是不是胡牌。

胡牌的定义为:
14张牌里面由一个对子,四个坎组成。其中对子代表两张一样的牌,坎代表三张一样的牌,或者三张连续的牌(连续指: 1s,2s,3s | 4t,5t,6t) 

举例:
1s,1s,2t,3t,4t,2t,3t,4t,6b,7b,8b,5s,5s,5s就是胡牌。


思路1 搜索+剪枝:
    step0:将牌排序

    step1: 枚举所有的牌,假设当前枚举到的牌记作 {x},然后尝试获取第2个x(/*如果没有则失败*/). 同时将2个x从手牌集合去除

    step2: 枚举所有的顺子(连续的牌).枚举顺子实际上是枚举顺子中最小的那个元素y,因为其他元素可以通过y+1,y+2来表示。并且从集合里面取出y,y+1,y+2;
    为了保持搜索的时候做到 /*不重不漏*/,从顺子里面最小的开始搜索,也就是说,这一次枚举了顺子 y+1,y+2,y+3,那么在接下来的递归搜索里,枚举的顺子最小元素一定从 {大于等于} y的元素开始;
    注意,如果y之后没有y+2了,那么就跳过(比如7s,8s,9s,1t,2t...),直接跳过8s,9s;

    step3: 不停搜索顺子,直到
        case1:已经有四个了,当前状态已经满足,直接返回。
        case2: 已经没有顺子了,但是还没有四个坎,于是需要寻找三个一样的元素。判定剩下的牌是不是满足条件很简单,这里就不多言了。

    优化1: 可以在去掉选中的顺子之后,提前评估一下当前的状态。如果任何一个元素的数量小于3,且不存在与其连续的其他元素,那么当前局面不可行,直接抛弃掉这个搜索。将这个评估函数记作`evaluate_state`.
    这个剪枝同样可以用在选中`对子`以后的评估。  
    优化2: 由于顺子只可能是相同种类的元素组成(1s,2t,3t是非法组合),所以我们可以将三种不同的种类分开处理。这样就将evaluate_state函数的检查范围缩小到一个种类的元素里面了。            

思路2: 打表+状态压缩

这个思路是针对有大量判定操作的需求来的。如果只有偶尔几次,不划算。

step1: 枚举所有的合法胡牌状态.这个做法并不难,将思路1里面的代码稍作修改即可。
step2: 对状态进行压缩。这里的压缩不是指对字符串进行压缩(比如将AAAABBBBCC -> 3A4B2C,然后用bit去表示3和A这种压缩),而是真正的减少状态的数量。
    2.1 为什么可以减少状态的数量?
    ans: 因为XXX ABC EFG MNP TT这种状态可以映射为很多胡牌原始状态,ABC可以替换为任何一个连续的顺子。TT可以替换为任何一个对子。
    2.2 怎么进行状态的压缩?
    ans: 很多人的直觉是对给定的状态转换成标准形式。然而,[转换到标准形式]这一步其实就是在判定是否胡牌,那么又回到了搜索的思路上去了。所以必须有一个快速的转换办法。实际上,可以考虑不使用标准状态,而使用一个更[压缩]的状态。
    2.3 更压缩的状态是什么?
    ans: 给定一个序列,对其进行排序得到有序集合 
    A = {1,1,1,1,2,2,2,2,3,3,3,3,9,9},可以压缩为 B = {4,4,4,2}.
    A' = {1,1,1,1,2,3,4,5,6,7,7,7,9,9},可以压缩为 B'={4,1,1,1,1,1,1,2,2}
    这就意味着:对于任何一种连续的序列,可以用连续的1来表示。对于相同的元素,用大于1的数字来表示。这就忽略了具体的元素,只考虑形式上的一致,同时是[排序后]的形式,这表示这里的2(9,9)可能可以映射为标准形式里面的1,2,3这种顺子的一部分,也可以映射为里面对子的一部分。而同样的标准型显然只有一种[更压缩状态]。
    2.4 怎么利用这种[更压缩状态]?
    有疑问是自然的,因为这种压缩虽然规避了[状态转换]这一步的困难(排序+遍历就可以搞定),但是损失了信息。也就是说给定状态即便在表里面,也未必是正确的的胡牌(很容易举出反例,这里省略)。所以我们需要补充信息。方法是将[更压缩形态]里面的分解模式记录下来(对于一种压缩状态,这样反变换的种类不会太多,同时注意去重)。即这种形态可以映射到哪些标准的形态。然后通过这种反变换,我们将给定的状态也变换到标准模式,看看满不满足胡牌的情景。
    这里给出一个例子,假设从标准状态A => [压缩状态]B,那么就在结果集合里面添加一个反映射表示S=[1,4,3,2,5,xx,xx...],它表示对B里面展开后的元素进行如S所示的shuffle(比如第二个元素放在第4个位置)就可以得到标准状态A。
    那么给定一个随机状态C,首先利用排序+转化变成状态B,然后检查状态B是否存在,若存在,获取所有模式反映射的表示,然后一一测试。由于变换到标准模式只是一次排序,而变幻后的模式非常容易测试是不是胡牌的,所以这个策略是可行的。
2.5 客户端的优化(提前过滤)
    由于这种压缩的状态其实占用内存很小,那么可以在客户端做一次提前检测,如果满足条件才向服务器进行二次测验(目的是防止别人修改客户端的代码,这样即使修改了也无法避开服务器的检测,所以修改没有意义)。

总结:这里有意思的新知识是:状态压缩可以通过这样巧妙的办法来避开,即可以快速转换到一个统一的形式,又不漏掉完整的信息。值得参考。

你可能感兴趣的:(常用算法)