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 相关文章推荐
cssQuery()的下载与使用方法
Jan 12 Javascript
Javascript匿名函数的一种应用 代码封装
Jun 27 Javascript
Firefox中autocomplete=&quot;off&quot; 设置不起作用Bug的解决方法
Mar 25 Javascript
JS this作用域以及GET传输值过长的问题解决方法
Aug 06 Javascript
getJSON调用后台json数据时函数被调用两次的原因猜想
Sep 29 Javascript
jQuery插件slider实现拖动滑块选取价格范围
Apr 30 Javascript
完美的js div拖拽实例代码
Sep 24 Javascript
javascript和jQuery中的AJAX技术详解【包含AJAX各种跨域技术】
Dec 15 Javascript
使用JavaScript为一张图片设置备选路径的方法
Jan 04 Javascript
bootstrap制作jsp页面(根据值让table显示选中)
Jan 05 Javascript
vue中简单弹框dialog的实现方法
Feb 26 Javascript
对Angular中单向数据流的深入理解
Mar 31 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
ThinkPHP之R方法实例详解
2014/06/20 PHP
PHP中cookie和session的区别实例分析
2014/08/28 PHP
php获取当前月与上个月月初及月末时间戳的方法
2016/12/05 PHP
Ajax+Jpgraph实现的动态折线图功能示例
2019/02/11 PHP
jQuery 创建Dom元素
2010/05/07 Javascript
Jquery 获得服务器控件值的方法小结
2010/05/11 Javascript
jsTree 基于JQuery的排序节点 Bug
2011/07/26 Javascript
基于jquery自己写tab滑动门(通用版)
2012/10/30 Javascript
jquery高级编程的最佳实践详解
2014/03/23 Javascript
javascript实现手机震动API代码
2015/08/05 Javascript
javascript跨域总结之window.name实现的跨域数据传输
2015/11/01 Javascript
asp.net+jquery.form实现图片异步上传的方法(附jquery.form.js下载)
2016/05/05 Javascript
Javascript 引擎工作机制详解
2016/11/30 Javascript
easyui关于validatebox实现多重规则验证的方法(必看)
2017/04/12 Javascript
Vue2.0 多 Tab切换组件的封装实例
2017/07/28 Javascript
基于vue中对鼠标划过事件的处理方式详解
2018/08/22 Javascript
Windows下支持自动更新的Electron应用脚手架的方法
2018/12/24 Javascript
Vue 表情包输入组件的实现代码
2019/01/21 Javascript
微信小程序实现的动态设置导航栏标题功能示例
2019/01/31 Javascript
layui自定义ajax左侧三级菜单
2019/07/26 Javascript
JS函数本身的作用域实例分析
2020/03/16 Javascript
[03:37]2016完美“圣”典 风云人物:Mikasa专访
2016/12/07 DOTA
[46:16]2018DOTA2亚洲邀请赛3月30日 小组赛B组 iG VS VP
2018/03/31 DOTA
[03:08]TI9战队档案 - Vici Gaming
2019/08/20 DOTA
Flask框架信号用法实例分析
2018/07/24 Python
python中scikit-learn机器代码实例
2018/08/05 Python
python面向对象入门教程之从代码复用开始(一)
2018/12/11 Python
python爬虫多次请求超时的几种重试方法(6种)
2020/12/01 Python
HTML5 Canvas标签使用收录
2009/07/07 HTML / CSS
中间件分为哪几类
2012/03/14 面试题
数控机械专业个人的自我评价
2014/01/02 职场文书
环保专业大学生职业规划设计
2014/01/10 职场文书
三严三实对照检查材料
2014/09/22 职场文书
群众路线领导班子整改方案
2014/10/25 职场文书
2014幼儿园中班工作总结
2014/11/10 职场文书
三八节活动主持词
2015/07/04 职场文书