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 相关文章推荐
Aptana调试javascript图解教程
Nov 30 Javascript
Prototype源码浅析 String部分(二)
Jan 16 Javascript
js控制表单不能输入空格的小例子
Nov 20 Javascript
JavaScript简单实现鼠标拖动选择功能
Mar 06 Javascript
Js Jquery创建一个弹出层可加载一个页面
May 08 Javascript
javascript Array 数组常用方法
Apr 05 Javascript
AngularJs bootstrap详解及示例代码
Sep 01 Javascript
关于iframe跨域POST提交的方法示例
Jan 15 Javascript
AngularJS中$http的交互问题
Mar 29 Javascript
vue2.0s中eventBus实现兄弟组件通信的示例代码
Oct 25 Javascript
js中对象和面向对象与Json介绍
Jan 21 Javascript
使用异步组件优化Vue应用程序的性能
Apr 28 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
这东西价格,可以买几台TECSUN S-2000
2021/03/02 无线电
PHP的栏目导航程序
2006/10/09 PHP
用PHP函数解决SQL injection
2006/12/09 PHP
php页面消耗内存过大的处理办法
2013/03/18 PHP
php selectradio和checkbox默认选择的实现方法详解
2013/06/29 PHP
PHP通过get方法获得form表单数据方法总结
2018/09/12 PHP
Laravel框架搜索分页功能示例
2019/02/01 PHP
多个iframe自动调整大小的问题
2006/09/18 Javascript
jquery的live使用注意事项
2014/02/18 Javascript
深入理解JavaScript的React框架的原理
2015/07/02 Javascript
跟我学习javascript的var预解析与函数声明提升
2015/11/16 Javascript
JavaScript地理位置信息API
2016/06/11 Javascript
Easyui Tree获取当前选择节点的所有顶级父节点
2017/02/14 Javascript
vue做移动端适配最佳解决方案(亲测有效)
2018/09/04 Javascript
vue计算属性computed、事件、监听器watch的使用讲解
2019/01/21 Javascript
vue+ElementUI 关闭对话框清空验证,清除form表单的操作
2020/08/06 Javascript
pyqt和pyside开发图形化界面
2014/01/22 Python
python中使用百度音乐搜索的api下载指定歌曲的lrc歌词
2014/07/18 Python
Python将DataFrame的某一列作为index的方法
2018/04/08 Python
Python爬虫框架scrapy实现downloader_middleware设置proxy代理功能示例
2018/08/04 Python
python生成器用法实例详解
2019/11/22 Python
Python+Redis实现布隆过滤器
2019/12/08 Python
TensorFlow2.X结合OpenCV 实现手势识别功能
2020/04/08 Python
Python调用JavaScript代码的方法
2020/10/27 Python
网络艺术零售业的先驱者:artrepublic
2017/09/26 全球购物
Stührling手表官方网站:男女高品质时尚手表的领先零售商
2021/01/07 全球购物
税务专业毕业生自荐信
2013/11/10 职场文书
文明家庭先进事迹材
2014/01/27 职场文书
驾驶员安全责任书
2014/07/22 职场文书
小学重阳节活动总结
2015/03/24 职场文书
2015年幼儿园教研活动总结
2015/03/25 职场文书
党支部工作总结2015
2015/04/01 职场文书
城南旧事观后感
2015/06/11 职场文书
2016公务员年度考核评语
2015/12/01 职场文书
HTML中的表单元素介绍
2022/02/28 HTML / CSS
Python OpenCV实现图像模板匹配详解
2022/04/07 Python