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 相关文章推荐
javascript对象之内置对象Math使用方法
Apr 16 Javascript
js实现动态添加、删除行、onkeyup表格求和示例
Aug 18 Javascript
Javascript让DEDECMS告别手写Tag
Sep 01 Javascript
jquery拖动层效果插件用法实例分析(附demo源码)
Apr 28 Javascript
如何判断Javascript对象是否存在的简单实例
May 18 Javascript
通过bootstrap全面学习less
Nov 09 Javascript
基于jQuery实现的打字机效果
Jan 16 Javascript
jQuery实现链接的title快速出现的方法
Feb 20 Javascript
微信小程序实现Session功能及无法获取session问题的解决方法
May 07 Javascript
JavaScript前端页面搜索功能案例【基于jQuery】
Jul 10 jQuery
js实现的在本地预览图片功能示例
Nov 09 Javascript
JQuery绑定事件四种实现方法解析
Dec 02 jQuery
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中使用Oracle数据库(6)
2006/10/09 PHP
php+AJAX传送中文会导致乱码的问题的解决方法
2008/09/08 PHP
解析PHP中VC6 X86和VC9 X86的区别及 Non Thread Safe的意思
2013/06/28 PHP
php通过array_merge()函数合并关联和非关联数组的方法
2015/03/18 PHP
jquery 简单图片导航插件jquery.imgNav.js
2010/03/17 Javascript
JavaScript子窗口ModalDialog中操作父窗口对像
2012/12/11 Javascript
图标线性回归斜着移动到指定的位置
2013/08/16 Javascript
node.js中的http.response.setHeader方法使用说明
2014/12/14 Javascript
scroll事件实现监控滚动条并分页显示(zepto.js)
2016/12/18 Javascript
Chrome浏览器的alert弹窗禁止再次弹出后恢复的方法
2016/12/30 Javascript
DWR3 访问WEB元素的两种方法实例详解
2017/01/03 Javascript
ES6字符串模板,剩余参数,默认参数功能与用法示例
2017/04/06 Javascript
详解Vue 方法与事件处理器
2017/06/20 Javascript
JavaScript面向对象程序设计创建对象的方法分析
2018/08/13 Javascript
Vue+Django项目部署详解
2019/05/30 Javascript
Node.js从字符串生成文件流的实现方法
2019/08/18 Javascript
Vue中多元素过渡特效的解决方案
2020/02/05 Javascript
[22:07]DOTA2-DPC中国联赛 正赛 iG vs Magma 选手采访
2021/03/11 DOTA
Python Matplotlib库入门指南
2015/05/18 Python
Python 多线程实例详解
2017/03/25 Python
Linux下python与C++使用dlib实现人脸检测
2018/06/29 Python
Python3.4学习笔记之常用操作符,条件分支和循环用法示例
2019/03/01 Python
浅谈python多进程共享变量Value的使用tips
2019/07/16 Python
解决TensorFlow GPU版出现OOM错误的问题
2020/02/03 Python
对Matlab中共轭、转置和共轭装置的区别说明
2020/05/11 Python
python通过cython加密代码
2020/12/11 Python
利用CSS3动画实现圆圈由小变大向外扩散的效果实例
2018/09/10 HTML / CSS
用canvas实现图片滤镜效果附演示
2013/11/05 HTML / CSS
html5拖拽应用记录及注意点
2020/05/27 HTML / CSS
打印机墨盒:123Inkjets
2017/02/16 全球购物
ALDI奥乐齐官方海外旗舰店:德国百年超市
2017/12/27 全球购物
经典c++面试题五
2014/12/17 面试题
类和结构的区别
2012/08/15 面试题
热门专业求职信
2014/05/24 职场文书
夫妻分居协议书范本(有子女版)
2014/11/01 职场文书
乡镇党的群众路线教育实践活动制度建设计划
2014/11/03 职场文书