性能最高的麻将算法 -- lua

作者: ZZ曾帅 | 来源:发表于2018-07-07 01:28 被阅读108次

    关于这个算法的由来

    今天是在公司写麻将算法的第四天,在第二天的时候已基本实现带N个癞子的麻将判胡算法,
    但是,性能太垃圾了,运行1万次就需要大概3秒左右,也就是我上一篇文章提及的算法,想一下如果全国1000万人同时玩麻将,有100服务器,那么每秒需要判断1000万次是否胡牌,每台服务器每秒需要判断10万次,那么每个人需要在0-30秒后才能得到结果,虽然这个只是大概数据,但是显然不符合实际,所以算法要从根本思路去颠覆

    新算法 -- 类二叉递归法

    废话不多说(如果要让我用文字去描述这个算法,那不如让我去死)
    为了让人更容易看懂,我使用了一个特例去画思维导图,结合代码运行,应该大家就可以慢慢理解到里面的神奇精妙之处

    这个案例没有讲解带N个癞子的情况,但是我相信理解了这个图和代码之后,带N个癞子的情况也能慢慢理解~
    好了,真的不废话了,上干货!


    麻将算法核心思路xmind图解.png

    再结合代码:

    
    -- @曾帅 2018/7/6
    
    ---------------------思路(伪代码)-------------
    --[=[
    检查胡牌:
      local 对子 = 找对子
      if 对子 then
        移除对子(对子)
        result
        if count == 0 then return result end
        检查胡牌()
        加回对子(对子)
      end
      lcoal 顺子= 找顺子()
      if 顺子 then
        移除顺子(顺子)
        result
        if count == 0 then return result end
        检查胡牌()
        加回顺子(顺子)
      end
      lcoal 刻子= 找刻子()
      if 刻子 then
        移除刻子(刻子)
        检查胡牌()
        加回刻子(刻子)
      end
    ]=]
    
    -- 定义胡牌成功的牌组数据结构
    local result = {
        dui_zi = nil,
        ke_zi_array = {},
        shun_zi_array = {},
    }
    
    -- 辅助函数,递归打印表,以便观察结果和测试查错等
    function PrintTable(tbl, level) -- level 为递归深度,默认为1
        if tbl == nil then return end
        local msg = ""
        level = level or 1
        local indent_str = "" -- 缩进
        for i = 1, level do
            indent_str = indent_str .. "  "
        end
        if level == 1 then
            print(indent_str .. "{")
        end
        for k, v in pairs(tbl) do
            local k_str = ""
            if type(k) == "string" then -- 键值为字符串,则["xxx"]
                k_str = string.format("[\"%s\"]", k)
            else
                k_str = string.format("[%s]", k)
            end
            if type(v) == "table" then
                local item_str = string.format("%s%s = {", indent_str .. "  ", k_str)
                print(item_str)
                PrintTable(v, level + 1)
            elseif type(v) == "number" then
                local item_str = string.format("%s%s = %s,", indent_str .. "  ", k_str, tostring(v))
                print(item_str)
            else
                local item_str = string.format("%s%s = \"%s\",", indent_str .. "  ", k_str, tostring(v))
                print(item_str)
            end
        end
        if level == 1 then
            print(indent_str .. "}")
        else
            print(indent_str .. "},")
        end
    end
    
    -- 在有序牌中找到第一个非个数为0的牌,最多需要遍历十次
    function GetFirstNotZeroKey(t, index)
        for i = 1, 10 do
            if t[i] and t[i] > 0 then
                return i + index - 1
            end
            if i == 10 then
                if t[99] and t[99] > 0 then
                    return 99
                end
            end
        end
    end
    
    -- 查找到ABC,记录ABC牌组
    -- 查找到AB,癞子足够,记录牌组和一个癞子
    -- 查找到AC,癞子足够,记录牌组和一个癞子
    -- 没查找到 返回false
    -- 优化的地方:这里的查找并不是真的查找
    -- 先拿到牌组中第一个个数不为0的牌(first_k)
    -- 再去找顺子,都是在一个table中找一个key对应的value
    -- 在lua中,这种操作时间复杂度都是O(1)
    -- 所以这个方法的时间复杂度为O(1),比之前的O(N^2)优化了许多
    function CheckAbc(t)
        local first_k = GetFirstNotZeroKey(t, 1)
        
        local zu_he = {first_k}
        local lai_zi_count = t[99]
        for k, v in ipairs({first_k + 1, first_k + 2, 99, 99}) do
            if t[v] and t[v] > 0 then
                if v == 99 and lai_zi_count == 0 then
                    break
                end
                table.insert(zu_he, v)
                if v == 99 then lai_zi_count = lai_zi_count - 1 end
                if #zu_he == 3 then break end
            end
        end
        
        return #zu_he == 3 and zu_he or false
    end
    
    -- 查找到AAA,记录AAA牌组
    -- 查找到AA,癞子足够,记录牌组和一个癞子
    -- 查找到A,癞子足够,记录牌组和两个癞子(特殊情况,癞子少的时候较少,癞子多的时候起优化作用)
    -- 原理和上面一致
    function Check3n(t)
        for k, v in pairs(t) do
            if v >= 3 then
                return {k, k, k}
            end
            if t[99] and t[99] >= 1 and v == 2 then
                return {k, k, 99}
            end
            if t[99] and t[99] >= 2 and v == 1 then
                return {k, 99, 99}
            end
            if t[99] and t[99] >= 3 and v == 0 then
                return {99, 99, 99}
            end
        end
    end
    
    -- 原理和上面一致
    function Check2n(t)
        for k, v in pairs(t) do
            if v >= 2 then
                return {k, k}
            end
            if t[99] and t[99] >= 1 then
                return {GetFirstNotZeroKey(t, 1), 99}
            end
            if t[99] and t[99] >= 2 then
                return {99, 99}
            end
        end
    end
    
    -- 把t2的元素加到t中,只是单纯把对应key的value加1,时间复杂度为O(1)
    function Insert(t, t1)
        for i, v in ipairs(t1) do
            t[v] = t[v] + 1
        end
        return t
    end
    -- 把t2的元素从t中移除,原理同上
    function Remove(t, t1)
        for i, v in ipairs(t1) do
            t[v] = t[v] - 1
        end
        return t
    end
    
    -- 递归去除(看简书上面的图解~),这里的删除添加操作与上面原理一样,时间复杂度为O(1)
    function Check(t)
        local res_2n = Check2n(t) -- 返回找到的对子
        if not result.dui_zi and res_2n then -- 如果找到对子,并且之前没有对子在记录中
            ---[[
            print("-------------find_2n-------------")
            PrintTable(res_2n)
            print("-------------删除前的t表-------------")
            PrintTable(t)
            --]]
            Remove(t, res_2n) -- 从牌组中移除对子
            ---[[
            print("-------------removed_2n-------------")
            PrintTable(t)
            --]]
            result.dui_zi = res_2n -- 记录好对子
            if not GetFirstNotZeroKey(t, 1) then return result end -- 如果移除完之后没有牌了,那么就返回结果
            if Check(t) then return result end -- 否则继续递归
            result.dui_zi = nil -- 清除对子
            Insert(t, res_2n) -- 还原对子到表中
            ---[[
            print("-------------insert_2n-------------")
            PrintTable(res_2n)
            print("-------------插入回去的t表-------------")
            PrintTable(t)
            --]]
        end
        
        -- 同上
        local res_abc = CheckAbc(t)
        if res_abc then
            ---[[
            print("-------------find_abc-------------")
            PrintTable(res_abc)
            print("-------------删除前的t表-------------")
            PrintTable(t)
            --]]
            Remove(t, res_abc)
            ---[[
            print("-------------removed_abc-------------")
            PrintTable(t)
            --]]
            table.insert(result.shun_zi_array, res_abc)
            if not GetFirstNotZeroKey(t, 1) then return result end
            if Check(t) then return result end
            table.remove(result.shun_zi_array)
            Insert(t, res_abc)
            ---[[
            print("-------------insert_abc-------------")
            PrintTable(res_abc)
            print("-------------插入回去t表-------------")
            PrintTable(t)
            --]]
        end
        
        -- 同上
        local res_3n = Check3n(t)
        if res_3n then
            ---[[
            print("-------------find_3n-------------")
            PrintTable(res_3n)
            print("-------------删除前的t表-------------")
            PrintTable(t)
            --]]
            Remove(t, res_3n)
            ---[[
            print("-------------removed_3n-------------")
            PrintTable(t)
            --]]
            table.insert(result.ke_zi_array, res_3n)
            if not GetFirstNotZeroKey(t, 1) then return result end
            if Check(t) then return result end
            table.remove(result.ke_zi_array)
            Insert(t, res_3n)
            ---[[
            print("-------------insert_3n-------------")
            PrintTable(res_3n)
            print("-------------插入回去的t表-------------")
            PrintTable(t)
            --]]
        end
        
        return false
    end
    
    -- 数出牌组中每个牌对应的个数,如牌是{1,1,1,3},则形成类似下面的表
    --[[
    {
        [1] = 3,
        [3] = 1,
    }
    --]]
    function TTT(tab)
        local tabT = {}
        table.sort(tab, function(a, b)
            return a < b
        end)
        for key, v in pairs(tab) do
            if tabT[tab[key]] == nil then
                tabT[tab[key]] = 1
            else
                tabT[tab[key]] = tabT[tab[key]] + 1
            end
        end
        return tabT
    end
    
    -- 判断是否能胡牌
    function CheckIsHu(t)
        if #t % 3 == 2 then -- 不符合基础规则(剔除大部分情况)
            -- 输出每张牌有多少个
            local count_t = {}
            count_t = TTT(t) -- 数表
            print("-------------------最开始的t表-----------------")
            PrintTable(count_t)
            
            local can_hu = Check(count_t) -- 递归验证
            if can_hu then
                print("-------------------最终胡牌组合-----------------")
                PrintTable(can_hu)
                return true
            end
            return false
        else
            return false
        end
    end
    
    print("--------------带N个赖子的胡牌算法-------------")
    
    -- 提供测试的数据
    -- local pai = {1, 99}
    -- local pai = {1, 1, 99}
    -- local pai = {1, 1, 2, 3, 99, 99, 99, 99}
    -- local pai = {1, 3, 5, 8, 8, 99, 99, 99}
    -- local pai = {1, 2, 3, 6, 8, 8, 8, 2, 3, 5, 5, 7, 7, 9, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}
    -- local pai = {1, 1, 1, 9, 4, 4, 5, 5, 6, 7, 7, 99, 99, 99} -- 复杂测试
    -- local pai = {1, 3, 3, 3, 5, 7, 7, 99, 99, 99, 99}
    -- local pai = {1, 2, 3, 5, 4, 99, 99, 99}
    -- local pai = {1, 2, 3, 5, 4, 5, 5, 6}
    -- local pai = {2, 3, 7, 9, 99, 99, 99, 99}
    -- local pai = {1,1,2,3,99,99,99,99}
    local pai = {1, 1, 2, 3, 4, 6, 6, 8}
    
    -- 测试运行时间
    local start_time = os.clock()
    local res
    res = CheckIsHu(pai)
    local end_time = os.clock()
    
    print(string.format("start_time   : %.6f", start_time))
    print(string.format("end_time   : %.6f", end_time))
    print(string.format("cost_time  : %.6f", end_time - start_time))
    
    -- 结果
    print("能否胡牌:" .. tostring(res))
    
    

    好了,希望即将要去面试棋牌游戏公司的童鞋们用这个算法去征服你们的HR吧~
    希望有更好的算法,愿意的话可以随时联系我 _
    注 : 未经允许,请不要随意转载
    有任何疑问或者是优化请联系我:
    邮箱 511793781@qq.com

    相关文章

      网友评论

        本文标题:性能最高的麻将算法 -- lua

        本文链接:https://www.haomeiwen.com/subject/ffyiuftx.html