此算法的实现,就是模拟人脑的思考和计算过程。
————————————————————————————-————————
1)
当我们拿到一个题目时,首先会根据已经知道的条件,进行数据的初步整理和分析。
相当于填写出9宫格里,所有的“确定项”,以及标记“可能选项”。
function refreshStat()
2)
此后,思考会进入 猜测/验证 的循环阶段。
在9宫格中,可以对于“可能选项”进行尝试,验证是否违背现有条件。
每一个新的分支,最后的结果无非是两种,答案/出错。
while(true){
var a=setOne();
var b=refreshStat();
if(!a||b){ //如果 a==false 或者 b==ture,则可以跳出循环
break;
}
}
实际人脑思考的过程,也是要先遍历选项较少的分支。
所以,程序实现上也是 确定点/2叉分支/3叉分支/....
3)
当所有的路径搜索下来,答案不是唯一的情况,是和数独游戏的宗旨相悖的。
————————————————————————————-————————
以下部分是全部代码,为方便阅读,调试信息未删除。
————————————————————————————-————————
1 <html> 2 <head> 3 <title>数独解题程序</title> 4 <meta http-equiv="Content-Type" content="text/html; charset=GBK" /> 5 <script> 6 function keygo(evt,obj){ 7 key = window .event?evt.keyCode:evt.which; 8 var next=obj.tabIndex ; 9 var inputs=document.getElementsByTagName("input"); 10 if (key==38){//↑ 11 if (next -9>=0 ) { 12 inputs[next-9].select() 13 } 14 } 15 if (key==40){//↓ 16 if (next +9<81 ) { 17 inputs[next+9].select() 18 } 19 } 20 if (key==37){//← 21 if (next -1>=0 ) { 22 inputs[next-1].select() 23 } 24 } 25 if (key==39){//→ 26 if(next+1<81)inputs[next+1].select(); 27 } 28 } 29 </script> 30 </head> 31 <body onload="init();"> 32 <div id="sodukuTable"></div><input type=button name="cal" onclick="calculate()" value="计算"> 33 <input type=button name="clear" onclick="clearGrid()" value="清空"> 34 <br><span><textarea id="gtxt" cols="19" rows="10">004502006 35 000000005 36 002014007 37 008000012 38 070080050 39 930020700 40 600190200 41 020000000 42 300208500</textarea></span> 43 <input type=button name="cal" onclick="paste()" value="粘贴" >可以文本拷贝到下框中后点粘贴,从左到右从上往下的81个数字序列,未填为0,中间非数字字符将忽略<br> 44 <span></span><br><span id="statusDiv"></span><span id="log"></span> 45 <script> 46 var maxRow =9; 47 var maxCol = 9; 48 var strTbody = ["<table id='sodukuTable' border='0'><tbody>"]; 49 for(var i = 0; i < maxRow; i++){ 50 strTbody.push("<tr>"); 51 for(var j = 0; j < maxCol; j++){ 52 strTbody.push("<td style='border-left:"+(j%3==0?1:0) 53 +"px solid black ;border-right:"+(j%3==2?1:0) 54 +"px solid black;border-top:"+(i%3==0?1:0) 55 +"px solid black;border-bottom:"+(i%3==2?1:0)+"px solid black;' ><input style='width:20px;' tabindex='"+(i*9+j) 56 +"'οnclick='check(this);' onKeyUp='return keygo(event,this)'type='text' id='input"+(i*9+j)+"'name='n"+(i*9+j)+"'value='"+i+j+"' ></td>"); 57 } 58 strTbody.push("</tr>"); 59 } 60 strTbody.push("</tbody></table>"); 61 var sTbody = ["<table border='1'><tbody>"]; 62 for(var i = 0; i < maxRow; i++){ 63 sTbody.push("<tr>"); 64 for(var j = 0; j < maxCol; j++){ 65 sTbody.push("<td style='border-left:"+(j%3==0?1:0) 66 +"px solid black ;border-right:"+(j%3==2?1:0) 67 +"px solid black;border-top:"+(i%3==0?1:0) 68 +"px solid black;border-bottom:"+(i%3==2?1:0)+"px solid black;'><div style='width:25px;height:25px' id='status"+(i*9+j)+"'name='div"+i+j+"' ></div></td>"); 69 } 70 sTbody.push("</tr>"); 71 } 72 sTbody.push("</tbody></table>"); 73 var obj = document.getElementById("sodukuTable"); 74 obj.innerHTML = strTbody.join(""); 75 var obj2 = document.getElementById("statusDiv"); 76 var grid=[ 77 [5, 7, 0, 1, 2, 0, 0, 0, 0], 78 [0, 0, 0, 0, 0, 0, 0, 0, 0], 79 [0, 0, 6, 7, 0, 0, 0, 8, 0], 80 [3, 0, 4, 0, 0, 9, 0, 7, 0], 81 [0, 2, 0, 0, 7, 0, 0, 5, 0], 82 [0, 1, 0, 3, 0, 0, 9, 0, 2], 83 [0, 8, 0, 0, 0, 2, 1, 0, 0], 84 [0, 0, 0, 0, 0, 0, 0, 0, 0], 85 [0, 0, 0, 0, 5, 4, 0, 6, 3]]; 86 var candidatNum=[]; 87 var columns=[]; 88 var rows=[]; 89 var blook=[]; 90 var papers=0; 91 var discards=0; 92 var success=false; 93 var steps = new Array(); 94 var log1 = document.getElementById("statusDiv"); 95 96 function Step(current1,arrys){ 97 this.temp1=new Array(); 98 this.step=[arrys[0],arrys[1],arrys[2]]; 99 for (var i = 0; i < 9; i++) 100 { 101 this.temp1[i]=new Array(); 102 for (var j = 0; j < 9; j++) 103 { 104 this.temp1[i][j]=current1[i][j]; 105 } 106 } 107 } 108 out(grid); 109 init(); 110 111 function push( current1, i, j, n) { 112 var s = new Step(current1, [ i, j, n ]); 113 steps.push(s); 114 } 115 function pop(){ 116 var step = steps.pop(); 117 discards ++; 118 grid=step.temp1; 119 grid[step.step[0]][step.step[1]] = step.step[2]; 120 var timeline = document.getElementById('PaperList'); 121 timeline.value += ('discard: ['+discards+']:['+papers+']\n'); 122 timeline.scrollTop = timeline.scrollHeight; 123 return step; 124 } 125 126 function check(obj){ 127 if(obj.value==0)return; 128 for(var i=0;i<9;i++){ 129 for(var j=0;j<9;j++){ 130 var text = document.getElementById("input"+(i*9+j)); 131 if(text.value==obj.value){ 132 text.style.background="green"; 133 }else{ 134 text.style.background=""; 135 } 136 } 137 138 } 139 140 } 141 function CheckNumInput(array,num, x, y) { 142 // 目标: 143 // 冲突检查 参数 array:矩阵 num:检测值 x/y:检测位置 144 // 行列宫均无冲突,return true; 145 // 发现冲突,return false; 146 if (((rows[x] & (1 << num)) == 0) && (columns[y] & (1 << num)) == 0 147 && (blook[parseInt(x / 3) * 3 + parseInt(y / 3)] & (1 << num)) == 0) { 148 return true; 149 } 150 return false; 151 } 152 153 function out(array){ 154 var result=true; 155 for (var i = 0; i < 9; i++) 156 { 157 for (var j = 0; j < 9; j++) 158 { 159 document.getElementById("input"+(i*9+j)).value=array[i][j]; 160 if(array[i][j]==0)result=false; 161 } 162 } 163 return result; 164 } 165 function setOne(){ 166 var result = false; 167 //目标: 168 // 遍历矩阵,当前是否可以简单刷新,或者已经可以发现出错. 169 for (var i = 0; i < 9; i++) { 170 for (var j = 0; j < 9; j++) { 171 //目标: 172 // (grid[i][j] == 0 && candidatNum[i][j][0] == 0) >> 没有候选数字,出错了 173 // (candidatNum[i][j][0] == 1) >> 候选数字唯一 174 // CheckNumInput(grid,candidatNum[i][j][10],i,j) >> 检查此数字是否符合逻辑 175 // 判断 没有候选数字||最后一个候选数字不符合逻辑的条件, 从这里回退或者返回出错 176 if (grid[i][j] == 0 && candidatNum[i][j][0] == 0|| 177 (candidatNum[i][j][0] == 1&&!CheckNumInput(grid,candidatNum[i][j][10],i,j))) { 178 if (grid[i][j] == 0) { 179 result = false; 180 } 181 if (steps.length>0) {// 回退 182 pop(); // 当前标签已经被证明逻辑错误,废弃 183 return true; 184 } else { 185 if (!success) { 186 alert("栈为空 结束!"); //题目出错,结束 187 } 188 return false; 189 } 190 } 191 if (candidatNum[i][j][0] == 1) { //唯一选择 192 grid[i][j] = candidatNum[i][j][10]; // 更新矩阵 193 refreshStat3(candidatNum[i][j][10],i,j); // 更新行列宫 194 candidatNum[i][j][0] = 0; // 标记已选 195 result = true; 196 continue; 197 } 198 199 } 200 } 201 if (result == false) { //对于(candidatNum[i][j][0] != 1)的情况,进行筛选 202 return choose(); 203 } 204 return result; 205 } 206 function refreshStat3( num, x, y) { // 更新行列宫 207 rows[x] |= 1<<num; 208 columns[y] |= 1<<num; 209 blook[parseInt(x / 3) * 3 + parseInt(y / 3)] |= 1 << num ; 210 211 } 212 /********************* 213 * 矩阵 数据分析 214 * 统计 剩余可选项 215 *********************/ 216 function refreshStat(){ 217 var over = true; 218 // 目标: 219 // 分解行/列/宫 220 for (var i = 0; i < 9; i++) { 221 rows[i] = 0; //行 222 columns[i] = 0; //列 223 blook[i] = 0; //宫 224 for (var j = 0; j < 9; j++) { 225 if (grid[i][j] != 0) { 226 rows[i] |= 1 << grid[i][j]; 227 } else { 228 rows[i] &= ~(1 << grid[i][j]); 229 } 230 if (grid[j][i] != 0) { 231 columns[i] |= 1 << grid[j][i]; 232 } else { 233 columns[i] &= ~(1 << grid[j][i]); 234 } 235 if (grid[parseInt(i / 3) * 3 + parseInt(j / 3)][i % 3 * 3 + j % 3] != 0) { 236 blook[i] |= 1 << grid[parseInt(i / 3 )* 3 + parseInt(j / 3)][i % 3 * 3 + j % 3]; 237 } 238 } 239 } 240 // 目标: 241 // 遍历矩阵,进行候选标记candidatNum[i][j][0] 242 // candidatNum[i][j][0] = 0; >>>> 已有确定值 243 // candidatNum[i][j][0] = k; >>>> 可能值数目 244 // candidatNum[i][j][1] = 987654321x 2进制数位表示的可选数字 245 for (var i = 0; i < 9; i++) { 246 for (var j = 0; j < 9; j++) { 247 if (grid[i][j] != 0) { 248 candidatNum[i][j][0] = 0; 249 continue; 250 } 251 var size = 0; 252 over = false; 253 for (var k = 1; k < 10; k++) { 254 if (CheckNumInput(grid, k, i, j)) { 255 candidatNum[i][j][1] |= 1 << k; 256 candidatNum[i][j][10] = k; 257 over = false; 258 size++; 259 } else { 260 candidatNum[i][j][1] &= ~(1 << k); 261 } 262 } 263 candidatNum[i][j][0] = size; //标记剩余选项数目 264 } 265 } 266 return over; 267 } 268 269 function calculate(){ //功能入口 270 //读取数据 271 var start=new Date(); 272 for (var i = 0; i < 9; i++) 273 { 274 for (var j = 0; j < 9; j++) 275 { 276 var text = document.getElementById("input"+(i*9+j)); 277 grid[i][j]=parseInt(text.value); 278 } 279 } 280 281 //刷新网格 282 refreshStat(); 283 out(grid); 284 //计算矩阵 285 while(true){ 286 var a=setOne(); 287 var b=refreshStat(); 288 if(!a||b){ //如果 a==false 或者 b==ture,则可以跳出循环 289 break; 290 } 291 } 292 out(grid); //答案 293 alert("用时:"+(new Date()-start)+"ms"); 294 success = true; 295 //计算结束 296 297 //验证答案是否唯一 298 if (papers != discards){ 299 if (steps.length>0) {// 回退 300 pop(); // 当前标签废弃 301 //计算矩阵 302 while(true){ 303 var a=setOne(); 304 var b=refreshStat(); 305 if(!a||b){ //如果 a==false 或者 b==ture,则可以跳出循环 306 break; 307 } 308 } 309 if (b) { 310 alert("答案不唯一!记录!"); 311 out(grid); //答案 312 } 313 else { 314 alert("答案唯一!!"); //答案唯一 315 } 316 } else { 317 alert("出错 结束!"); 318 return false; 319 } 320 } 321 } 322 function clearGrid(){ 323 for (var i = 0; i < 9; i++){ 324 for (var j = 0; j < 9; j++){ 325 grid[i][j]=0; 326 document.getElementById("input"+(i*9+j)).value=grid[i][j]; 327 } 328 } 329 out(grid); 330 } 331 function init(){ 332 for (var i = 0; i < 9; i++) 333 { candidatNum[i]=new Array(); 334 335 for (var j = 0; j < 9; j++) 336 { candidatNum[i][j]=new Array(); 337 338 for (var k = 0; k < 11; k++) 339 { candidatNum[i][j][k]=0; 340 } 341 } 342 } 343 } 344 function choose() { 345 //目标: 346 // 遍历矩阵,从当前位置建立搜索分支. 347 var binarynode = false; 348 for (var i = 0; i < 9; i++) { 349 for (var j = 0; j < 9; j++) { 350 // 2叉树分支: 351 // 如果在某位置有两种可能,选项1/选项2 352 // 则假设是选项1,并进行验算,同时按选项2生成一个新的标签 353 if (candidatNum[i][j][0] == 2) {// 有2种选择 354 binarynode = true; 355 var found = -1; 356 for (var k = 1; k < 10; k++) { 357 if ((candidatNum[i][j][1] & (1 << k)) > 0) { 358 if (found > 0) { 359 papers ++; 360 var timeline = document.getElementById('PaperList'); 361 timeline.value += ('add papers:'+papers+':'+i+' '+j+' '+k+'\n'); 362 timeline.scrollTop = timeline.scrollHeight; 363 push(grid, i, j, k); 364 }else{ 365 found = k; 366 } 367 } 368 } 369 grid[i][j] = found; 370 candidatNum[i][j][0] = 0; // 在当前标签上标记已选 371 var timeline = document.getElementById('PaperList'); 372 timeline.value += ('TRY CURRENT:'+i+' '+j+' '+found+'\n'); 373 timeline.scrollTop = timeline.scrollHeight; 374 return true; 375 } 376 } 377 } 378 if (!binarynode) { 379 var timeline = document.getElementById('PaperList'); 380 timeline.value += ('2叉分支未找到!\n'); 381 timeline.scrollTop = timeline.scrollHeight; 382 for (var i = 0; i < 9; i++) { 383 for (var j = 0; j < 9; j++) { 384 // 2叉树查找失败时,启动3叉树分支,作为扩充,还可以启动3叉树分支: 385 // 如果在某位置有三种可能,选项1/选项2/选项3 386 // 则假设是选项1,并进行验算,同时按选项2生成一个新的标签 387 if (candidatNum[i][j][0] == 3) {// 有3种选择 388 var timeline = document.getElementById('PaperList'); 389 timeline.value += ('发现3叉分支!\n'); 390 timeline.scrollTop = timeline.scrollHeight; 391 binarynode = true; 392 var found = -1; 393 for (var k = 1; k < 10; k++) { 394 if ((candidatNum[i][j][1] & (1 << k)) > 0) { 395 if (found > 0) { 396 papers ++; 397 var timeline = document.getElementById('PaperList'); 398 timeline.value += ('add papers:'+papers+':'+i+' '+j+' '+k+'\n'); 399 timeline.scrollTop = timeline.scrollHeight; 400 push(grid, i, j, k); 401 }else{ 402 found = k; 403 } 404 } 405 } 406 grid[i][j] = found; 407 candidatNum[i][j][0] = 0; // 在当前标签上标记已选 408 var timeline = document.getElementById('PaperList'); 409 timeline.value += ('TRY CURRENT:'+i+' '+j+' '+found+'\n'); 410 timeline.scrollTop = timeline.scrollHeight; 411 return true; 412 } 413 } 414 } 415 } 416 return false; 417 } 418 function paste(){ 419 var gridstr= document.getElementById("gtxt").value; 420 var a = gridstr.replace(/[^0-9]/g,''); 421 if(a.length!=81){ 422 alert("数据格式不正确:\n"+gridstr); 423 return; 424 } 425 for (var i = 0; i < 9; i++) 426 { 427 for (var j = 0; j < 9; j++){ 428 grid[i][j]=a.charAt(i*9+j); 429 document.getElementById("input"+(i*9+j)).value=grid[i][j]; 430 } 431 } 432 out(grid); 433 papers = 0; 434 discards = 0; 435 success = false; 436 steps = new Array(); 437 } 438 </script> 439 建议使用IE浏览器,F12开启调试模式<br> 440 <br><span> 441 <textarea id="PaperList" cols="30" rows="20" style="position:absolute;left:550px;"></textarea></span> 442 </body> 443 </html>