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 相关文章推荐
jsTree树控件(基于jQuery, 超强悍)[推荐]
Sep 01 Javascript
$.get获取一个文件的内容示例代码
Sep 11 Javascript
javascript获取鼠标点击元素对象(示例代码)
Dec 20 Javascript
JavaScript中函数表达式和函数声明及函数声明与函数表达式的不同
Nov 15 Javascript
轻松掌握jQuery中wrap()与unwrap()函数的用法
May 24 Javascript
js正则表达式验证表单【完整版】
Mar 06 Javascript
vue、react等单页面项目应该这样子部署到服务器
Jan 03 Javascript
详解redux异步操作实践
Aug 15 Javascript
微信小程序实现简易table表格
Jun 19 Javascript
微信小程序登录时如何获取input框中的内容
Dec 04 Javascript
Vue 3.0 全家桶抢先体验
Apr 28 Javascript
JavaScript原型链详解
Nov 07 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
ThinkPHP3.1新特性之对Ajax的支持更加完善
2014/06/19 PHP
golang与PHP输出excel示例
2016/07/22 PHP
JavaScript 对象、函数和继承
2009/07/07 Javascript
JavaScript 继承详解 第一篇
2009/08/30 Javascript
Extjs学习笔记之四 工具栏和菜单
2010/01/07 Javascript
Array 重排序方法和操作方法的简单实例
2014/01/24 Javascript
检测一个函数是否是JavaScript原生函数的小技巧
2015/03/13 Javascript
JS动态创建DOM元素的方法
2015/06/09 Javascript
Bootstrap按钮下拉菜单组件详解
2016/05/10 Javascript
jQuery使用each方法与for语句遍历数组示例
2016/06/16 Javascript
jQuery拖拽通过八个点改变div大小
2020/11/29 Javascript
jQuery实现单击按钮遮罩弹出对话框效果(2)
2017/02/20 Javascript
JS实现浏览器打印、打印预览示例
2017/02/28 Javascript
浅谈vue中慎用style的scoped属性
2017/11/28 Javascript
Vue.js图片预览插件使用详解
2018/08/27 Javascript
Javascript中绑定click事件的四种方式介绍
2018/10/26 Javascript
JS实现图片拖拽交换效果
2018/11/30 Javascript
实例分析Array.from(arr)与[...arr]到底有何不同
2019/04/09 Javascript
在vue中使用cookie记住用户上次选择的实例(本次例子中为下拉框)
2020/09/11 Javascript
[01:32]TI奖金增速竟因它再创新高!DOTA2勇士令状不朽珍藏Ⅰ饰品欣赏
2018/05/18 DOTA
解决pyqt中ui编译成窗体.py中文乱码的问题
2016/12/23 Python
[原创]使用豆瓣提供的国内pypi源
2017/07/02 Python
python清除函数占用的内存方法
2018/06/25 Python
Python实现数据可视化看如何监控你的爬虫状态【推荐】
2018/08/10 Python
python实现遍历文件夹修改文件后缀
2018/08/28 Python
numpy按列连接两个维数不同的数组方式
2019/12/06 Python
CSS3 text-shadow实现文字阴影效果
2016/02/24 HTML / CSS
ECCO俄罗斯官网:北欧丹麦鞋履及皮具品牌
2020/06/26 全球购物
eBay荷兰购物网站:eBay.nl
2020/06/26 全球购物
如何启动时不需输入用户名与密码
2014/05/09 面试题
大学本科毕业生的自我鉴定
2013/11/26 职场文书
职工运动会邀请函
2014/02/02 职场文书
2014年最新个人对照检查材料范文
2014/09/25 职场文书
婚庆答谢词大全
2015/09/29 职场文书
MySQL的prepare使用以及遇到的bug
2022/05/11 MySQL
微软Win11 全新照片应用面向 Dev预览版推出 新版本上手体验图集
2022/09/23 数码科技