DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> JavaScript遍歷求解數獨問題的主要思路小結
JavaScript遍歷求解數獨問題的主要思路小結
編輯:關於JavaScript     

數獨規則
數獨游戲,經典的為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;                      //成功找到一組解
}

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved