JavaScript遍历求解数独问题的主要思路小结


Posted in Javascript onJune 12, 2016

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

数独技巧

  • 直观法
  • 候选数法
  • 相关二十格:一个数字只与其所在行列及小九宫格的二十格相关

我的思路

  • 精心设计了有效性判定函数,最多一次遍历81个小单元格就能做出方案的有效性判定。
  • 同理设计了相关20格判定,一次0~9的循环就完成有效性判定。
  • 用数组模拟堆栈,为搜索提供回溯信息。
  • 利用对象具有map性质,来辅助判断方案的有效性,大大简化了算法。

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

1.变量定义:

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;

2.方案有效性判定:
充分利用了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;                //程序能运行到这,说明方案有效
}
3.相关二十格判定
原理同整体判定,亮点在小九宫格的定位上。
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;
}

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

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;                      //成功找到一组解
}
Javascript 相关文章推荐
在b/s开发中经常用到的javaScript技术
Aug 23 Javascript
js自动生成的元素与页面原有元素发生堆叠的解决方法
Sep 04 Javascript
javascript实现加载xml文件的方法
Nov 24 Javascript
JavaScript弹出对话框的三种方式
Mar 23 Javascript
AngularJS控制器继承自另一控制器
May 09 Javascript
Bootstrap和Angularjs配合自制弹框的实例代码
Aug 24 Javascript
jquery实现下拉框左右选择功能
Feb 21 Javascript
vue货币过滤器的实现方法
Apr 01 Javascript
Angular4.0动画操作实例详解
May 10 Javascript
Node.js 实现远程桌面监控的方法步骤
Jul 02 Javascript
浅谈vue中$event理解和框架中在包含默认值外传参
Aug 07 Javascript
js基于div丝滑实现贝塞尔曲线
Sep 23 Javascript
Node.js环境下编写爬虫爬取维基百科内容的实例分享
Jun 12 #Javascript
JavaScript解八皇后问题的方法总结
Jun 12 #Javascript
jQuery遍历json的方法(推荐)
Jun 12 #Javascript
jQuery移动端图片上传组件
Jun 12 #Javascript
jQuery通过ajax请求php遍历json数组到table中的代码(推荐)
Jun 12 #Javascript
JavaScript中实现键值对应的字典与哈希表结构的示例
Jun 12 #Javascript
JavaScript中输出信息的方法(信息确认框-提示输入框-文档流输出)
Jun 12 #Javascript
You might like
PHP is_array() 检测变量是否是数组的实现方法
2016/06/13 PHP
PHP简单读取xml文件的方法示例
2017/04/20 PHP
php编程实现简单的网页版计算器功能示例
2017/04/26 PHP
js控制input输入字符解析
2013/12/27 Javascript
JavaScript中Function函数与Object对象的关系
2015/12/17 Javascript
基于jquery实现简单的分页控件
2016/03/17 Javascript
创建一个类Person的简单实例
2016/05/17 Javascript
js中获取时间new Date()的全面介绍
2016/06/20 Javascript
JS实现对中文字符串进行utf-8的Base64编码的方法(使其与Java编码相同)
2016/06/21 Javascript
a标签跳转到指定div,jquery添加和移除class属性的实现方法
2016/10/10 Javascript
移动端js图片查看器
2016/11/17 Javascript
BootStrap3中模态对话框的使用
2017/01/06 Javascript
简单实现jQuery多选框功能
2017/01/09 Javascript
Ajax基础知识详解
2017/02/17 Javascript
react实现菜单权限控制的方法
2017/12/11 Javascript
vue中导出Excel表格的实现代码
2018/10/18 Javascript
解决iview多表头动态更改列元素发生的错误的方法
2018/11/02 Javascript
Node.js Event Loop各阶段讲解
2019/03/08 Javascript
angular 表单验证器验证的同时限制输入的实现
2019/04/11 Javascript
vue中使用百度脑图kityminder-core二次开发的实现
2019/09/26 Javascript
OpenLayers加载缩放控件使用方法详解
2020/09/25 Javascript
vue内置组件keep-alive事件动态缓存实例
2020/10/30 Javascript
[01:03:59]2018DOTA2亚洲邀请赛3月30日 小组赛B组VGJ.T VS Secret
2018/03/31 DOTA
python中enumerate函数用法实例分析
2015/05/20 Python
详解Python最长公共子串和最长公共子序列的实现
2018/07/07 Python
postman模拟访问具有Session的post请求方法
2019/07/15 Python
python re模块匹配贪婪和非贪婪模式详解
2020/02/11 Python
Python实现封装打包自己写的代码,被python import
2020/07/12 Python
Django生成数据库及添加用户报错解决方案
2020/10/09 Python
PyCharm+Miniconda3安装配置教程详解
2021/02/16 Python
css3 iphone玻璃透明气泡完美实现
2013/03/20 HTML / CSS
CSS3感应鼠标的背景闪烁和图片缩放动画效果
2014/05/14 HTML / CSS
香港零食网购:上仓胃子
2020/06/08 全球购物
前台领班岗位职责
2013/12/04 职场文书
撤回我也能看到!教你用Python制作微信防撤回脚本
2021/06/11 Python
Mysql数据库值的添加、修改、删除及清空操作实例
2021/06/20 MySQL