资讯专栏INFORMATION COLUMN

数独求解(javascript实现)

Berwin / 1341人阅读

摘要:数独技巧直观法候选数法相关二十格一个数字只与其所在行列及小九宫格的二十格相关我的思路精心设计了有效性判定函数,最多一次遍历个小单元格就能做出方案的有效性判定。

看《算法的乐趣》,试着用非递归穷举来解数独,看效率如何!

数独规则

数独游戏,经典的为9×9=81个单元格组成的九宫格,同时也形成了3×3=9个小九宫格,要求在81个小单元格中填入数字1~9,并且数字在每行每列及每个小九宫格中都不能重复。

数独技巧

直观法

候选数法

相关二十格:一个数字只与其所在行列及小九宫格的二十格相关

我的思路

精心设计了有效性判定函数,最多一次遍历81个小单元格就能做出方案的有效性判定。

同理设计了相关20格判定,一次0~9的循环就完成有效性判定。

用数组模拟堆栈,为搜索提供回溯信息。

利用对象具有map性质,来辅助判断方案的有效性,大大简化了算法。

方案设计与实现

只用了一个二维数组存储数独方案,一个一维数组作堆栈,一个布尔变量作回溯标识。

变量定义
var problem = [                //这是书上提到的难度10.7的题
    [8,0,0,0,0,0,0,0,0],
    [0,0,3,6,0,0,0,0,0],
    [0,7,0,0,9,0,2,0,0],
    [0,5,0,0,0,7,0,0,0],
    [0,0,0,0,4,5,7,0,0],
    [0,0,0,1,0,0,0,3,0],
    [0,0,1,0,0,0,0,6,8],
    [0,0,8,5,0,0,0,1,0],
    [0,9,0,0,0,0,4,0,0]
]
var stack = [],flag = false;
方案有效性判定

充分利用了javascript对象的哈希特性,为了方便调试,判定有效时函数的返回值为0,无效时分三种情况,行冲突、列冲突、小九宫格冲突,分别返回1,2,3。前期判定用了它,后来增加了相关二十格判定,在找答案时这个函数就用不上了。

function checkValid(sudo){
    let subSudo = {}                        //辅助变量,用来判定小九宫格是否冲突
    for(let i = 0; i<9; i++){
        let row = {}, col = {}              //辅助变量,用来判定行、列是否冲突
        for(let j = 0; j<9; j++){
            let cur1 = sudo[i][j], cur2 = sudo[j][i]            //一次内循环同时完成行列的判定
            if(row[cur1])                    //当前元素已经在行中出现,优化掉零的判断,key为0时值为0,不需要额外判断
                return 1;                    //返回错误代码
            else
                row[cur1] = cur1            //当前元素未在行中出现,存入辅助变量中   
            if(col[cur2])                    //列的判定与行类似,优化掉零的判断,key为0时值为0,不需要额外判断
                return 2;
            else
                col[cur2] = cur2;
            let key = Math.floor(i/3)+"-"+Math.floor(j/3)        //为不同的小九宫格生成不同的key
            if(subSudo[key]){                 //小九宫格中已经有元素,优化掉零的判断,key为0时值为0,不需要额外判断
                if(subSudo[key][cur1])        //对某一个小九宫格的判定与行类似
                    return 3
                else
                    subSudo[key][cur1] = cur1
            }else{                            //这是某小九宫格中的第一个元素
                subSudo[key] = {}             //为小九宫格新建一个辅助变量,并将第一个元素存入其中
                subSudo[key][cur1] = cur1
            }                  
        }
    }
    return 0;                                //程序能运行到这,说明方案有效
}
相关二十格判定

原理同整体判定,亮点在小九宫格的定位上。

function check20Grid(sudo,i,j){                
    let row = {}, col = {}, subSudo = {}                //辅助变量
    for(let k = 0; k < 9; k++){
        let cur1 = sudo[i][k], cur2 = sudo[k][j]
        if(cur1){                                        //当前元素已经在行中出现,优化掉零的判断,key为0时值为0,不需要额外判断
            if(row[cur1])
                return 1;                                //返回错误代码
            else
                row[cur1] = cur1                        //当前元素未在行中出现,存入辅助变量中
        }
        if(cur2){                                        //列的判定与行类似,优化掉零的判断,key为0时值为0,不需要额外判断
            if(col[cur2])
                return 2;
            else
                col[cur2] = cur2;
        }
        //转化循环变量到小九宫格的坐标
        let key = sudo[Math.floor(i/3)*3 + Math.floor(k/3)][Math.floor(j/3)*3+Math.floor(k%3)]
        if(subSudo[key])                                //九宫格判定与行类似,优化掉零的判断,key为0时值为0,不需要额外判断
            return 3
        else
            subSudo[key] = key
    }
    return 0;
}
遍历求解

利用元素状态初值为零的元素即为待定的特性,并加上堆栈的辅助,没有再开辟额外的存储空间。

function findAnswer(){
    for(let i = 0; i<9; i++){
        for(let j = 0; j<9; ){
            if(problem[i][j] === 0 || flag){              //当前位置为待定元素的首次处理或回溯到当前位置,两种情况看似不同,其实处理相同,自加1即可
                flag = false;
                let k = problem[i][j] + 1;                //搜索向下一个合法值迈进
                while(k<10){                              //循环找到下一个合法值
                    problem[i][j] = k;                    //填值
                    if(check20Grid(problem,i,j) == 0){    //判定合法,相关二十格判定
                        stack.push([i,j++])               //存储回溯点,并步进
                        break;
                    }
                    k++;
                }
                if(k>9){                                  //当前位置找不到合法值,回溯
                    problem[i][j] = 0;                    //回溯前归零
                    let rt = stack.pop();                 //堆栈中取回溯信息
                    if(!rt)                               //无解判断,返回0
                        return 0;    
                    i=rt[0]                               //穿越
                    j=rt[1]
                    flag = true;
                }
            }else{                                        //当前位置数字为题目给定
                j++;
            }
        }
    }
    return 1;                                            //成功找到一组解
}
完整代码

代码托管在开源中国,其中的sudoku.js即数独解法。

https://git.oschina.net/zhoutk/test.git
答案

书上提到的难度为10.7的题目的答案,1秒内解决,效率还行。

[ [ 8, 1, 2, 7, 5, 3, 6, 4, 9 ],
  [ 9, 4, 3, 6, 8, 2, 1, 7, 5 ],
  [ 6, 7, 5, 4, 9, 1, 2, 8, 3 ],
  [ 1, 5, 4, 2, 3, 7, 8, 9, 6 ],
  [ 3, 6, 9, 8, 4, 5, 7, 2, 1 ],
  [ 2, 8, 7, 1, 6, 9, 5, 3, 4 ],
  [ 5, 2, 1, 9, 7, 4, 3, 6, 8 ],
  [ 4, 3, 8, 5, 2, 6, 9, 1, 7 ],
  [ 7, 9, 6, 3, 1, 8, 4, 5, 2 ] ]

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79284.html

相关文章

  • [Java] 数独生成和求解

    摘要:首先在二维数组第一行随机填充个数字,然后将这个数字随机分布到整个二维数组中,然后使用求解数独的算法对此时的数组进行求解,得到一个完整的数独,然后按照用户输入的提示数量进行随机挖洞,得到最终的数独题目。 思路 1.生成数独 数独的生成总体思路是挖洞法。首先在二维数组第一行随机填充1-9 9个数字,然后将这9个数字随机分布到整个二维数组中,然后使用求解数独的算法对此时的数组进行求解,得到一...

    skinner 评论0 收藏0
  • 数独X--Android openCV识别数独并自动求解填充APP开发过程

    摘要:可以针对笔者常用的数独本文的实现都基于该,实现数独的识别求解并把答案自动填入。专家级别的平均秒完成求解包括图像数字提取,识别过程,完成全部操作。步骤四数独求解,生成答案,并生成需要填充的数字序列。 1 序   数独是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3...

    yvonne 评论0 收藏0
  • 《AI之矛》(1)【数独Agent】

    摘要:而此处针对进一步的搜索,有两个问题需要考虑如何选取搜索起点方格确定哪种搜索策略深度优先搜索,广度优先搜索关于第一个问题,无论选择哪个方格起始搜索,对于能否解决问题来说并不存在差异。 Github仓库地址 学习是为了寻找解决问题的答案,若脱离了问题只为知晓而进行的打call,那么随时间流逝所沉淀下来的,估计就只有重在参与的虚幻存在感了,自学的人就更应善于发现可供解决的问题。为了入门AI,...

    CatalpaFlat 评论0 收藏0
  • LeetCode36.有效的数独 JavaScript

    摘要:上图是一个部分填充的有效的数独。数独部分空格内已填入了数字,空白格用表示。但由于位于左上角的宫内有两个存在因此这个数独是无效的。说明一个有效的数独部分已被填充不一定是可解的。只需要根据以上规则,验证已经填入的数字是否有效即可。 判断一个 9x9的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次...

    elva 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<