日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

4.9 数独问题

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

1. 前言

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

2. 問題描述

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

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

3. 問題分析

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

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

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

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

4. 代碼

1 : Candidate 存儲每一個方格的備選數(shù)目的數(shù)據(jù)結(jié)構(gòu)

/*** file name : Candidates.java* created at : 7:58:14 PM Apr 22, 2015* created by 970655147*/package com.hx.sudoku02;// 備選的數(shù)據(jù)集合 public class Candidate {// 當(dāng)前對象 對應(yīng)的方格的行, 列 以及備選的數(shù)據(jù)集合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中數(shù)據(jù)為[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;}// 復(fù)制當(dāng)前的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 : 數(shù)獨數(shù)據(jù)結(jié)構(gòu)

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

3 Main : 主類, 主要包含了數(shù)獨的構(gòu)造的形式, 以及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";// 構(gòu)造Sudoku對象 并解析 輸出結(jié)果, 計算時間Sudoku sudoku = new Sudoku(s10_2); // sudoku.printSudoku();sudoku.parse();long spent = System.currentTimeMillis() - start;Log.log("all spent : " + spent + " ms");// 判斷給定的字符串是否符合數(shù)獨的性質(zhì) // String s10_2 = "867453192 413628579 174896253 259317846 386945721 945182637 531274968 728569314 692731485"; // Log.log(Tools.isResultIsSudoku(Tools.parseSudoku(s10_2)));}}

5. 運行結(jié)果

6. 總結(jié)

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

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

總結(jié)

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

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。