日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

4.9 数独问题

發布時間:2023/12/29 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 4.9 数独问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 前言

本文的一些圖片, 資料 截取自編程之美

2. 問題描述

額, 4.9 這里的問題是 給定一個殘缺的數獨, 粗魯額的計算一下大概有多少種解法, 以及多少種獨立的解答, 。。。一看 這個便是一個數學問題,,,
算了 跳過算了, 跳到了1.9, 這個是關于如果解決填充殘缺的數獨的問題的

其實, 對于數獨的解法, 我也是在貼吧看見一個家伙, 在說, 貌似是那家伙要找一個提高他自己的解決數獨效率, 然后 后來我自己沒事情的時候, 就寫了寫 思路 是基于書中的第一種思路

3. 問題分析

解法一 : 獲取所有的空格子的所有可能的取值, 遍歷所有的空格子, 嘗試填充所有的可能, 當不符合數獨的特性的時候, 進行回溯, 將當前的空格子設置為當前空格的下一種可能的數字, 如果當前空格沒有了可能的數字, 則回溯到前一個空格, 設置該空格的值為該空格的下一種可能, 然后繼續嘗試當前空格的所有可能, 這種思路能夠找到所有的解, 但是復雜度較高

解法二 : 非用言語能夠表達.. 上圖吧

請注意 后面有一句, 這種方法并不能解出所有的情況 !
犧牲了一定程度的正確性

我的思路
我的這里的思路是基于第一種解法的, 不過每次假設值的時候, 找出備選數最少的坐標假設值

4. 代碼

1 : Candidate 存儲每一個方格的備選數目的數據結構

/*** file name : Candidates.java* created at : 7:58:14 PM Apr 22, 2015* created by 970655147*/package com.hx.sudoku02;// 備選的數據集合 public class Candidate {// 當前對象 對應的方格的行, 列 以及備選的數據集合private Integer row;private Integer col;private List<Integer> candidates;// 初始化public Candidate() {candidates = new ArrayList<Integer>();}public Candidate(List<Integer> candidates, int row, int col) {this.candidates = candidates;this.row = row;this.col = col;}// setter & getterpublic void putCandidate(Integer candidate) {this.candidates.add(candidate);}public Integer getCandidate() {return candidates.remove(candidates.size() - 1);}public boolean hasCandidates() {return candidates.size() > 0;}public int size() {return candidates.size();}public Integer getRow() {return row;}public Integer getCol() {return col;}// for debug ...// 如果candidates中數據為[1, 2, 3] 返回1, 2, 3public String toString() {if(candidates.size() == 0) {return "[...]";}StringBuilder sb = new StringBuilder(candidates.size() * 3);for(int i=0; i<candidates.size(); i++) {sb.append(candidates.get(i) + ", ");}return sb.substring(0, sb.length() - 2);}// 獲取除了給定的val的其他的candidatespublic Candidate updateCandidate(int val) {List<Integer> dstCandidates = new ArrayList<Integer>(candidates.size());for(int i=0; i<candidates.size(); i++) {if(candidates.get(i).intValue() != val) {dstCandidates.add(candidates.get(i));}}return new Candidate(dstCandidates, row, col);}// 判定兩個Candidate對象相同public boolean equals(Candidate can) {for(int i=0; i<candidates.size(); i++) {if(candidates.get(i).intValue() != can.candidates.get(i).intValue()) {return false;}}return true;}// 復制當前的Candidate對象public Candidate copy() {List<Integer> dstCandidates = new ArrayList<Integer>(candidates.size());for(int i=0; i<candidates.size(); i++) {dstCandidates.add(candidates.get(i));}return new Candidate(dstCandidates, row, col);}}

2 Sudoku : 數獨數據結構

/*** file name : Sudoku.java* created at : 7:57:10 PM Apr 22, 2015* created by 970655147*/package com.hx.sudoku02;// 數獨類 // 我去, 原來還需要在每一宮都只出現一次,, 我以為我就做完了呢,,, --2015.04.24 10:26 // 現在應該算是 做完了吧, 只不過效率沒有 貼吧那家伙的高[現在去看看他的算法吧] 找到第一個結果需要240ms 為什么需要進行深拷貝哪里也不懂, 一直沒有找出問題在哪里...?? --2015.04.24 14:38 public class Sudoku {// 當前計算的數獨的數據, 最原始的給定的數據, 每一個方格對應的Candidateprivate Integer[][] data;private Integer[][] backupData;private Candidate[][] candidates;// 共有多少個結果, 有多少個方格確定了private int count;private int unSetted;// 方格的個數 每一宮的邊長的方格的個數public final static int GRID_NUM = 81;public final static int LITTLE_GRID_EDGE = 3;// 初始化public Sudoku() {}public Sudoku(String data) {// 初始化data, backupData, candidatesthis.data = Tools.parseSudoku(data);backupData = this.data;initCandidates(this.data);unSetted = GRID_NUM;}// 解析數獨 如果data為null 拋出異常public void parse() {if(data == null) {throw new RuntimeException("data can't be null...");}doParse();}// 具體的解析數獨// 先獲取備選數據最少的方格 // 在依次假設該方格的值為所有的備選數據 更新該方格對應行, 列的所有方格// 判斷是否失敗 或者成功 如果是 則恢復之前設置的備選數據為之前的值 [應該是可以優化的]// 否則繼續遞歸doParse ->private void doParse() {// 找到備選數最少的方格 創建一個minSizeCandidate的副本 用于假設該方格的數據, 這樣的話不會更改minSizeCandidateCandidate minSizeCandidate = getMinCandidate(candidates);Candidate copyMinSizeCandidate = minSizeCandidate.copy();boolean isEnd = false;// 遍歷所有的備選數據while(candidateNotNull(copyMinSizeCandidate)) {// 存儲之前的candidates, minSizeCandidate對應的位的數據Candidate[][] oldCandidates = this.candidates;Integer oldData = data[minSizeCandidate.getRow()][minSizeCandidate.getCol()];// 假設minSizeCandidate對應的方格的值, 更新該方格對應的行, 列的所有方格的Candidatedata[minSizeCandidate.getRow()][minSizeCandidate.getCol()] = copyMinSizeCandidate.getCandidate();// unSetted在updateCandidates中更新this.candidates = updateCandidates(minSizeCandidate);// 如果失敗了 設置isEnd為true 之后跳出循環 失敗的檢測需要隨時檢測if(isFailed(this.candidates)) {isEnd = true;}// 優化2. // 如果未設置的方格為為0 才檢測是否成功 添加了一個unSetted變量, 但是感覺這里也沒有優化多少,,, 大概30ms 1230 -> 1200if(unSetted == 0) {Log.log(data);count ++;isEnd = true;}// 如果沒有失敗/ 成功, 遞歸doParseif(!isEnd) {doParse();}// 恢復minSizeCandidate對應的方格的數據isEnd = false;data[minSizeCandidate.getRow()][minSizeCandidate.getCol()] = oldData;this.candidates = oldCandidates;}}// 判斷是否當前數獨的假設失敗了 判定的條件是假設任意一個方格的不為null的Candidate的備選數據的個數小于1個 則視為失敗// 因為該方格沒有備選數據了private boolean isFailed(Candidate[][] candidates) {for(int i=0; i<candidates.length; i++) {Candidate[] row = candidates[i];for(int j=0; j<candidates.length; j++) {if(row[j] == null) {continue;}if(row[j].size() < 1) {return true;}}}return false;}// 更新minSizeCandidate對應的方格對應行, 列 其他方格的Candidateprivate Candidate[][] updateCandidates(Candidate minSizeCandidate) {Candidate[][] updatedCandidates = new Candidate[this.candidates.length][this.candidates.length]; unSetted = GRID_NUM;// 遍歷candidates上minSizeCandidate對應的行或者 列 上的所有方格 [如果該為的數據已經確定 設置該Candidate為null] 更新該方格的Candidate[這里的算法應該可以優化]// 其他的方格的Candidate不變[淺拷貝]for(int i=0; i<candidates.length; i++) {Candidate[] row = candidates[i];for(int j=0; j<candidates.length; j++) { // row[j]為空表示該i, j處的數據以確定, // data[i][j]不為0 表示該位數據以確定[或者被假設]if(data[i][j] != 0) {unSetted --;updatedCandidates[i][j] = null;// 我去 之前少一個continue....continue;}// 2. 為什么進行深拷貝才能計算出結果...>???? --2015.04.24// 進行深拷貝是因為 如果不進行深拷貝的話下面的n次遞歸的過程中 坑定會更改這個Candidate, 這個操作時不可逆的, 因為之前沒有備份, // 那么更改了這個Candidate之后 如果之后因為失敗或者成功的原因回溯回去 下一次在訪問這個方格的Candidate的時候 他的備選數據 已經變少了 導致忽略了很多方格的備選數據, 最終導致 結果可能計算不出來// 所以 這里 我才用了一個比較巧妙的方法, 就是在doParse之前將minSizeCandidate備份一下 minSizeCandidate.getCandidate假設數據的時候 就用copyOfMinSizeCandidate 這樣更改的就是minSizeCandidate的副本了// 從而深復制一個minSizeCandidate 而不用深復制minSizeCandidate更新數據是其他行列 并且不在同一個宮格內的方格的Candidate了 // 520ms -> 380msif(i==minSizeCandidate.getRow() || j==minSizeCandidate.getCol() || Tools.isInSameGrid(i, j, minSizeCandidate.getRow(), minSizeCandidate.getCol()) ) {updatedCandidates[i][j] = row[j].updateCandidate(data[minSizeCandidate.getRow()][minSizeCandidate.getCol()]);} else {updatedCandidates[i][j] = row[j];}}}return updatedCandidates;}// 判斷res是否匹配最開始給定的數據[匹配每一個非待填的數據]private boolean isResultMatchData(Integer[][] res) {// 遍歷backupData[最開始的data], res 如果兩者中任意一個非待填數據不相等 則返回falsefor(int i=0; i<backupData.length; i++) {for(int j=0; j<backupData.length; j++) {if(backupData[i][j] != 0) {if(backupData[i][j] != res[i][j]) {return false;}}}}return true;}// 根據data初始化 candidatesprivate void initCandidates(Integer[][] data) {candidates = new Candidate[data.length][data.length];// 遍歷data 如果該方格數據為0 則獲取該方格的Candidate 設置到candidates[i][j]中// 否則設置candidates[i][j]為nullfor(int i=0; i<data.length; i++) {Integer[] row = data[i];for(int j=0; j<row.length; j++) {if(data[i][j] == 0) {candidates[i][j] = getCandidate(data, i, j);} else {candidates[i][j] = null;}}}}// 根據data 獲取第row行, col列對應的方格的Candidate對象private Candidate getCandidate(Integer[][] data, int row, int col) {// 創建candidates對象 并添加0-9到其中List<Integer> candidates = new ArrayList<Integer>(data.length);Tools.initAllCandidates(candidates, data.length);// x表示第幾行, y表示第幾列// 移除row行中所有的已經確定的數for(int i=0; i<data.length; i++) {if(data[row][i] != 0) {candidates.remove(data[row][i]);}}// 移除col列中有的已經確定的數for(int i=0; i<data.length; i++) {if(data[i][col] != 0) {candidates.remove(data[i][col]);}}// 移除當前宮中確定的數Point leftUp = Tools.getLeftUpGridPos(row, col);Point rightDown = new Point(leftUp.x + LITTLE_GRID_EDGE, leftUp.y + LITTLE_GRID_EDGE);for(int i=leftUp.x; i<rightDown.x; i++) {for(int j=leftUp.y; j<rightDown.y; j++) {if(data[i][j] != 0) {candidates.remove(data[i][j]);}}}// 根據candidates, row, col創建Candidate對象return new Candidate(candidates, row, col);}// 獲取備選數據最少的方格private Candidate getMinCandidate(Candidate[][] candidates) {// 找到第一個存在備選數據的方格Candidate minSizeCandidate = findFirstCandidate(candidates);Point p = new Point();p.x = minSizeCandidate.getRow();p.y = minSizeCandidate.getCol();// 遍歷所有的之后所有的方格 找到備選數據最少的方格for(int i=p.x; i<candidates.length; i++) {Candidate[] row = candidates[i];for(int j=p.y; j<row.length; j++) {if(data[i][j]==0 && candidateNotNull(row[j])) {if(row[j].size() < minSizeCandidate.size()) {minSizeCandidate = row[j];}}}}return minSizeCandidate;}// 找到第一個存在備選數據的方格 將其行, 列存儲在p中[其實現在沒有必要了, 因為candidate中有行, 列的數據] 返回該Candidateprivate Candidate findFirstCandidate(Candidate[][] candidates) {for(int i=0; i<candidates.length; i++) {Candidate[] row = candidates[i];for(int j=0; j<row.length; j++) {if(candidateNotNull(row[j])) {return row[j];}}}return null;}// 表示該candidate還有備選的數據private boolean candidateNotNull(Candidate candidate) {return candidate!=null && candidate.hasCandidates();}// 打印當前的數獨public void printSudoku() {Log.log(data);}}

3 Main : 主類, 主要包含了數獨的構造的形式, 以及Sudoku的使用方法

/*** file name : Main.java* created at : 7:54:31 PM Apr 22, 2015* created by 970655147*/package com.hx.sudoku02;// Main public class Main {// 主要是用于計算找到的第一個解的時間開銷public static long startTime = 0;public static void main(String []args) {long start = System.currentTimeMillis();startTime = start;String s11_9 = "000000039 000001005 003050800 008090006 070002000 100400000 009080050 020000600 400700000";String s10_2 = "800000000 003600000 070090200 050007000 000045700 000100030 001000068 008500010 090000400";String s8_2= "000070009 009800000 070065040 040601070 302000000 005200600 000009800 001400000 600000003";String s7_4= "006004000 000200005 100900042 021000003 000000097 300000008 000000000 005030070 014826000";String s6_7= "090004508 000020340 006000002 060003009 030000700 140000000 800010000 370000900 009008030"; String simpleSudoku02 = "000000010 400000000 020000000 000050604 008000300 001090000 300400200 050100000 000807000";// 構造Sudoku對象 并解析 輸出結果, 計算時間Sudoku sudoku = new Sudoku(s10_2); // sudoku.printSudoku();sudoku.parse();long spent = System.currentTimeMillis() - start;Log.log("all spent : " + spent + " ms");// 判斷給定的字符串是否符合數獨的性質 // String s10_2 = "867453192 413628579 174896253 259317846 386945721 945182637 531274968 728569314 692731485"; // Log.log(Tools.isResultIsSudoku(Tools.parseSudoku(s10_2)));}}

5. 運行結果

6. 總結

這里的思路 也是類似于窮舉的思路

注 : 因為作者的水平有限,必然可能出現一些bug, 所以請大家指出!

總結

以上是生活随笔為你收集整理的4.9 数独问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。