[原]数独小解

栏目: 编程工具 · 发布时间: 5年前

内容简介:本文简述了一种关于数独游戏的程序解法数独游戏相信大家都有所耳闻,规则上非常简明:这里有个示例:

本文简述了一种关于数独游戏的程序解法

数独游戏相信大家都有所耳闻,规则上非常简明:

在 9 x 9 的数格中(由 9 个 3 x 3 的九宫格组成)会预先给定不少于(>=)17个数字,你需要在满足以下条件的情况下补完其余数格中的数字:

  1. 每一行的数字不重复
  2. 每一列的数字不重复
  3. 每个 3 x 3 九宫格中的数字不重复

这里有个示例:

[原]数独小解

使用程序求解数独的一种简单方法便是穷举(搜索),较粗糙的一种方式就是枚举 9 x 9 数格中所有的数字组合(为每个数格指定 1 ~ 9 之间的某个数字),只是这种搜索方式的解空间较大,实际运用中需要进行剪枝,剪枝的思路其实也很朴素,依据数独中相关数字不可重复的3条规则即可:

按照空白数格顺序进行深度优先搜索,对于每个空白数格生成候选数字列表(即满足规则下可能填入该空白数格的数字列表),然后依次填入可能的候选数字,并在下一个空白数格处继续进行相同的深度优先搜索,如果成功则找到答案,否则返回失败.

( 可以使用启发式算法优化,譬如按候选数字列表最短的空白数格顺序进行搜索(实际测试中因为工程原因其实比顺序搜索方法更慢) )

下面是使用 Lua 实现的示例程序:

function get_sudoku_row_and_col(data)
    local row = #data
    local col = data[1] and #data[1] or 0
    return row, col
end

function print_sudoku(data)
    local row, col = get_sudoku_row_and_col(data)
    for i = 1, row do
        local str_buffer = {}
        for j = 1, col do
            table.insert(str_buffer, data[i][j])
        end
        print(table.concat(str_buffer, ", "))
    end
    print()
end

function check_sudoku_row(data, row_index)
    local row, col = get_sudoku_row_and_col(data)
    local sum = 1
    for i = 1, col do
        sum = sum | (1 << (data[row_index][i]))
    end
    return sum == 0x3FF
end

function check_sudoku_col(data, col_index)
    local row, col = get_sudoku_row_and_col(data)
    local sum = 1
    for i = 1, row do
        sum = sum | (1 << (data[i][col_index]))
    end
    return sum == 0x3FF
end

function check_sudoku_square(data, row_index, col_index)
    local row, col = get_sudoku_row_and_col(data)
    local sum = 1
    for i = row_index, row_index + 2 do
        for j = col_index, col_index + 2 do
            sum = sum | (1 << (data[i][j]))
        end
    end
    return sum == 0x3FF
end

function check_sudoku(data)
    local row, col = get_sudoku_row_and_col(data)
    
    -- check row
    for i = 1, row do
        if not check_sudoku_row(data, i) then
            return false
        end
    end
    
    -- check col
    for i = 1, col do
        if not check_sudoku_col(data, i) then
            return false
        end
    end
    
    -- check square
    for i = 1, row // 3 do
        local row_index = (i - 1) * 3 + 1
        for j = 1, col // 3 do
            local col_inex = (j - 1) * 3 + 1
            if not check_sudoku_square(data, row_index, col_inex) then
                return false
            end
        end
    end
    
    return true
end

function apply_sudoku(data, delta)
    local row, col = delta.row, delta.col
    if data[row] and data[row][col] then
        data[row][col] = delta.val
    end
end

function revert_sudoku(data, delta)
    local row, col = delta.row, delta.col
    if data[row] and data[row][col] then
        data[row][col] = 0
    end
end

function get_pending_list(data, row, col)
    if data[row] and data[row][col] then
        if data[row][col] == 0 then
            -- only empty pos has pending list
            local pending_list = { true, true, true, true, true, true, true, true, true }
            
            local data_row, data_col = get_sudoku_row_and_col(data)
            
            -- check row
            for i = 1, data_col do
                pending_list[data[row][i]] = false
            end
            
            -- check col
            for i = 1, data_row do
                pending_list[data[i][col]] = false
            end
            
            -- check square
            local square_row = (row - 1) // 3 * 3 + 1 -- or math.ceil(row / 3) * 3 - 2
            local square_col = (col - 1) // 3 * 3 + 1 -- or math.ceil(col / 3) * 3 - 2
            for i = square_row, square_row + 2 do
                for j = square_col, square_col + 2 do
                    pending_list[data[i][j]] = false
                end
            end
            
            -- gen pending list number
            local pending_list_num = {}
            for i = 1, #pending_list do
                if pending_list[i] then
                    table.insert(pending_list_num, i)
                end
            end
            
            return pending_list_num
        end
    end
end

function get_next_search_pos(data, row, col)
    -- now we just search in order
    local data_row, data_col = get_sudoku_row_and_col(data)
    
    -- top right part
    for j = col, data_col do
        if data[row][j] == 0 then
            return row, j
        end
    end
    
    -- rest part
    for i = row + 1, data_row do
        for j = 1, data_col do
            if data[i][j] == 0 then
                return i, j
            end
        end
    end
end

function search_sudoku_recur(data, row, col)
    row, col = get_next_search_pos(data, row, col)
    if row and col then
        local pending_list = get_pending_list(data, row, col)
        if pending_list and #pending_list > 0 then
            for i = 1, #pending_list do
                apply_sudoku(data, { row = row, col = col, val = pending_list[i] })
                local val = search_sudoku_recur(data, row, col)
                if val then
                    return true
                else
                    revert_sudoku(data, { row = row, col = col, val = pending_list[i] })
                end
            end
            
            -- all pending list is not valid
            return false
        else
            -- no valid pending list
            return false
        end
    else
        -- all data filled
        if check_sudoku(data) then
            -- valid data
            return true
        else
            -- invalid data
            return false
        end
    end
end

function search_sudoku(data)
    return search_sudoku_recur(data, 1, 1)
end

示例程序并不简短,但是理解起来并不困难,要点如下:

  • 数独的数格表示使用了"二维数组"
  • 函数 check_sudoku 用以检查数格中的数字是否满足数独条件
  • 函数 search_sudoku 实现了上述的深度优先搜索方法

下图是号称世界上迄今难度最大的数独游戏,有兴趣的朋友可以试下:

[原]数独小解

参考资料

  1. 数独的维基百科
  2. <算法的乐趣>
  3. 世上最难数独

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

反应式设计模式

反应式设计模式

Roland Kuhn、Brian Hanafee、Jamie Allen / 何品、邱嘉和、王石冲、林炜翔审校 / 清华大学出版社 / 2019-1-1 / 98.00 元

《反应式设计模式》介绍反应式应用程序设计的原则、模式和经典实践,讲述如何用断路器模式将运行缓慢的组件与其他组件隔开、如何用事务序列(Saga)模式实现多阶段事务以及如何通过分片模式来划分数据集,分析如何保持源代码的可读性以及系统的可测试性(即使在存在许多潜在交互和失败点的情况下)。 主要内容 ? “反应式宣言”指南 ? 流量控制、有界一致性、容错等模式 ? 得之不易的关于“什么行不通”的经验 ? ......一起来看看 《反应式设计模式》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

MD5 加密
MD5 加密

MD5 加密工具