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 相关文章推荐
锋利的jQuery 要点归纳(三) jQuery中的事件和动画(上:事件篇)
Mar 24 Javascript
EasySlider 基于jQuery功能强大简单易用的滑动门插件
Jun 11 Javascript
有关JavaScript的10个怪癖和秘密分享
Aug 28 Javascript
基于Jquery+Ajax+Json的高效分页实现代码
Oct 29 Javascript
JS打印gridview实现原理及代码
Feb 05 Javascript
JavaScript实现的石头剪刀布游戏源码分享
Aug 22 Javascript
让JavaScript中setTimeout支持链式操作的方法
Jun 19 Javascript
javascript中的Function.prototye.bind
Jun 25 Javascript
用jQuery获取table中行id和td值的实现代码
May 19 Javascript
使用Vue如何写一个双向数据绑定(面试常见)
Apr 20 Javascript
angular4自定义表单控件[(ngModel)]的实现
Nov 23 Javascript
react中hook介绍以及使用教程
Dec 11 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
咖啡店都有些什么常规豆子呢?有什么风味在里面
2021/03/04 咖啡文化
浅谈php安全性需要注意的几点事项
2014/07/17 PHP
CakePHP框架Session设置方法分析
2017/02/23 PHP
PHP正则验证字符串是否为数字的两种方法并附常用正则
2019/02/27 PHP
thinkphp框架表单数组实现图片批量上传功能示例
2020/04/04 PHP
php7 参数、整形及字符串处理机制修改实例分析
2020/05/25 PHP
JavaScript全局函数使用简单说明
2011/03/11 Javascript
jQuery中(function(){})()执行顺序的理解
2013/03/05 Javascript
本地图片预览(支持IE6/IE7/IE8/Firefox3)经验总结
2013/03/25 Javascript
extjs render 用法介绍
2013/09/11 Javascript
jquery选择器之层级过滤选择器详解
2014/01/27 Javascript
jQuery使用prepend()方法在元素前添加内容用法实例
2015/03/26 Javascript
JavaScript识别网页关键字并进行描红的方法
2015/11/09 Javascript
用JavaScript动态建立或增加CSS样式表的实现方法
2016/05/20 Javascript
jQuery Ajax 全局调用封装实例代码详解
2016/06/02 Javascript
jQuery插件imgAreaSelect基础讲解
2017/05/26 jQuery
gulp安装以及打包合并的方法教程
2017/11/19 Javascript
jQuery实现的两种简单弹窗效果示例
2018/04/18 jQuery
深入理解JavaScript的async/await
2018/08/05 Javascript
[03:19]2016国际邀请赛中国区预选赛第四日TOP10镜头集锦
2016/07/01 DOTA
一个超级简单的python web程序
2014/09/11 Python
python实现简单点对点(p2p)聊天
2017/09/13 Python
分数霸榜! python助你微信跳一跳拿高分
2018/01/08 Python
浅谈numpy生成数组的零值问题
2018/11/12 Python
Python面向对象总结及类与正则表达式详解
2019/04/18 Python
Python supervisor强大的进程管理工具的使用
2019/04/24 Python
python+opencv实现摄像头调用的方法
2019/06/22 Python
linux下安装python3和对应的pip环境教程详解
2019/07/01 Python
Python使用import导入本地脚本及导入模块的技巧总结
2019/08/07 Python
树莓派安装OpenCV3完整过程的实现
2019/10/10 Python
python 实现矩阵填充0的例子
2019/11/29 Python
python列表的逆序遍历实现
2020/04/20 Python
写出SQL四条最基本的数据操作语句(DML)
2012/12/12 面试题
初中生期末评语大全
2014/04/24 职场文书
护士节活动总结
2014/08/29 职场文书
大学新生入学感想
2015/08/07 职场文书