深度優先搜尋_之數獨遊戲
1. 問題描述:
你一定聽說過“數獨”遊戲。 如下圖所示,玩家需要根據9×9盤面上的已知數字,推理出所有剩餘空格的數字,並滿足每一行、每一列、每一個同色九宮內的數字均含1-9,不重複。 數獨的答案都是唯一的,所以,多個解也稱為無解。 本圖的數字據說是芬蘭數學家花了3個月的時間設計出來的較難的題目。但對會使用計算機程式設計的你來說,恐怕易如反掌了。 本題的要求就是輸入數獨題目,程式輸出數獨的唯一解。我們保證所有已知資料的格式都是合法的,並且題目有唯一的解。 格式要求,輸入9行,每行9個數字,0代表未知,其它數字為已知。 輸出9行,每行9個數字表示數獨的解。 輸入:
005300000 800000020 070010500 400005300 010070006 003200080 060500009 004000030 000009700
程式應該輸出: 145327698 839654127 672918543 496185372 218473956 753296481 367542819 984761235 521839764
再例如,輸入: 800000000 003600000 070090200 050007000 000045700 000100030 001000068 008500010 090000400
程式應該輸出: 812753649 943682175 675491283 154237896 369845721 287169534 521974368 438526917 796318452
數獨遊戲使用深度優先搜尋的方法是比較方便的,可以搜出數獨的所有合法的解(使用其他的方法來解決是非常困難的或者是不可能的)
深度優先搜尋是自上而下進行搜尋,直到搜尋出第一個合法的解返回,然後退到上一層搜尋它的兄弟,對它的兄弟然後繼續遞迴搜尋下去,在迴圈中嘗試著每一條路徑,假如某一條路不能夠走下去那麼它也會退到上一層然後在迴圈中嘗試著下一個解,即走下一條可能走得通的路徑然後繼續遞迴走下去,所以說深度優先搜尋能夠搜尋出所有的可能,而這種解決方法是其他迭代的方法不能夠解決的
深度優先搜尋:先縱後橫,先走一條能夠到達終點的路徑然後遇到遞迴的出口那麼退回到上一層迴圈呼叫它的地方搜它的兄弟,每一層遞迴完成之後都會往上一層退回去然後繼續遞迴下去
深度優先搜尋通常是迴圈中巢狀遞迴,搜尋完所有的路徑後最終退出遞迴,其中可能涉及到回溯的問題
以數獨遊戲為例,當迴圈到某一個二維陣列的某一個位置的時候那麼假如填的數字合法那麼對下一個空的陣列的位置進行填寫數字,但是如果經過迴圈之後發現沒有一個數字可以填入可以填入這個空的位置那麼迴圈結束之後遞迴退到上一層,在上一層中尋找其他的結果,假如其他的解合法那麼會繼續進行下一次的遞迴走下一條路,但是有可能在上一層中的其他數字除了之前填入的那麼數字合法其他都不合法,那麼填入的上一層的這個數字是不合法的,上一層的迴圈結束,此時就需要使用到回溯把之前填的這個不合法的數字清掉,重置為零,重新退到再上一層嘗試再上一層的其他解然後繼續走下去
每個位置上都可能存在著1-9這個範圍的某些解,那麼經過篩選之後可能只剩下一些解,當嘗試到某個解合法的時候繼續填下一個位置那麼
那麼當其他的層遞迴完成之後退到這一層的時候通過迴圈嘗試著其他的可能的合法解,直到搜尋完全部的解就結束了
這裡使用到回溯是為了清除掉某個位置中不合法的解,然後退到上一層再呼叫下一層的時候陣列才不會亂套
3.深度優先搜尋經典的程式碼都是迴圈中巢狀遞迴的方法來進行實現的,然後在遞迴的時候加上遞迴的出口,因為這道題目只有唯一的解,那麼假如求解到第一個解之後我們可以直接退出系統,而不是使用return,因為使用return的話會退到上一層搜尋它的兄弟,而這道題是不需要這樣的,其他的題目可能需要搜出所有解那麼就要使用return,具體的程式碼如下: import java.util.Scanner; public class Main{ public static void main(String[] args) { Scanner sc = new Scanner(System.in); char arr[][] = new char[9][]; for(int i = 0; i < 9; i++) { arr[i] = sc.nextLine().toCharArray(); } dfs(arr, 0, 0); }
//深度優先搜尋 private static void dfs(char[][] arr, int x, int y) { if(x == 9){ print(arr); //因為這道題目限制了只有唯一的解那麼不求出一個解之後直接退出不要使用return //入股哦使用return的話會搜尋它的兄弟 System.exit(0); } if(arr[x][y] == '0'){ for(int i = 1; i < 10; i++){ if(check(arr, x, y, i)){ arr[x][y] = (char)('0' + i); dfs(arr, x + (y + 1) / 9, (y + 1) % 9); } } //回溯: 因為有可能不滿足條件返回這一層的時候只有這個數字滿足條件那麼這個數字迴圈下去都沒有數字滿足條件 //那麼需要把這個數字重新恢復為零返回上一層繼續遞迴嘗試再上一層的其他解 //填空的時候每個空都有1-9個數字進行選擇然後進行篩選看一下哪一個符合條件 等到它下面的兄弟都完了之後再嘗
//試這一層的其他合法解 arr[x][y] = '0'; }else{ //System.out.println("zhang"); dfs(arr, x + (y + 1) / 9, (y + 1) % 9); } }
private static boolean check(char[][] arr, int x, int y, int k){ for(int i = 0; i < 9; i++){ if(arr[x][i] == (char)('0' + k) || arr[i][y] == (char)('0' + k)) return false; } //檢查九宮格 for(int i = (x / 3) * 3; i < (x / 3 + 1) * 3; i++){ for(int j = (y / 3) * 3; j < (y / 3 + 1) * 3; j++){ if(arr[i][j] == (char)('0' + k)){ return false; } } } return true; }
private static void print(char[][] arr) { for(int i = 0; i < 9; i++){ for(int j = 0; j < 9; j++){ System.out.print(arr[i][j]); } System.out.print("\n"); } } }