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 css函数用法(判断标签是否拥有某属性)
May 28 Javascript
JS的千分位算法实现思路
Jul 31 Javascript
用javascript添加控件自定义属性解析
Nov 25 Javascript
Jquery注册事件实现方法
May 18 Javascript
完全深入学习Bootstrap表单
Nov 28 Javascript
Angular2 自定义validators的实现方法
Jul 05 Javascript
javascript 作用于作用域链的详解
Sep 27 Javascript
优化Vue项目编译文件大小的方法步骤
May 27 Javascript
d3.js 地铁轨道交通项目实战
Nov 27 Javascript
VueCli4项目配置反向代理proxy的方法步骤
May 17 Javascript
解决在Vue中使用axios POST请求变成OPTIONS的问题
Aug 14 Javascript
JavaScript 空间坐标的使用
Aug 19 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+DBM的同学录程序(4)
2006/10/09 PHP
PHP高级对象构建 多个构造函数的使用
2012/02/05 PHP
PHP获取和操作配置文件php.ini的几个函数介绍
2013/06/24 PHP
PHP中调用SVN命令更新网站方法
2015/01/07 PHP
两种php给图片加水印的实现代码
2020/04/18 PHP
Apache启动报错No space left on device: AH00023该怎么解决
2015/10/16 PHP
YII Framework框架教程之日志用法详解
2016/03/14 PHP
php使用escapeshellarg时中文被过滤的解决方法
2016/07/10 PHP
php微信开发之音乐回复功能
2018/06/14 PHP
解决laravel session失效的问题
2019/10/14 PHP
在Ajax中使用Flash实现跨域数据读取的实现方法
2010/12/02 Javascript
myFocus slide3D v1.1.0 使用方法与下载
2011/01/12 Javascript
js操作iframe的一些方法介绍
2013/06/25 Javascript
用js的document.write输出的广告无阻塞加载的方法
2014/06/05 Javascript
详细分析JS函数去抖和节流
2017/12/05 Javascript
JavaScript遍历数组的三种方法map、forEach与filter实例详解
2019/02/27 Javascript
微信小程序 轮播图实现原理及优化详解
2019/09/29 Javascript
JS实现简易留言板特效
2019/12/23 Javascript
浅析JavaScript 函数柯里化
2020/09/08 Javascript
Vue select 绑定动态变量的实例讲解
2020/10/22 Javascript
基于elementUI竖向表格、和并列的案例
2020/10/26 Javascript
[47:45]Liquid vs OG 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
Python使用回溯法子集树模板解决爬楼梯问题示例
2017/09/08 Python
Python简单获取网卡名称及其IP地址的方法【基于psutil模块】
2018/05/24 Python
python 应用之Pycharm 新建模板默认添加编码格式-作者-时间等信息【推荐】
2019/06/17 Python
python字符串格式化方式解析
2019/10/19 Python
Python一行代码解决矩阵旋转的问题
2019/11/30 Python
python 经典数字滤波实例
2019/12/16 Python
python wsgiref源码解析
2021/02/06 Python
Ivory Isle Designs美国/加拿大:婚礼和活动文具公司
2018/08/21 全球购物
干部考核评语
2014/04/29 职场文书
十佳少年事迹材料
2014/12/25 职场文书
兵马俑的导游词
2015/02/02 职场文书
婚礼双方父亲致辞
2015/07/27 职场文书
2016党性教育学习心得体会
2016/01/21 职场文书
go语言使用Casbin实现角色的权限控制
2021/06/26 Golang