1. 程式人生 > >數獨求解演算法(回溯法和唯一解法)java實現

數獨求解演算法(回溯法和唯一解法)java實現

數獨(すうどく,Sudoku)是一種運用紙、筆進行演算的邏輯遊戲。玩家需要根據9×9盤面上的已知數字,推理出所有剩餘空格的數字,並滿足每一行、每一列、每一個粗線宮內的數字均含1-9,不重複。     注:數獨的各種知識和解決思路請 參考http://www.llang.net/sudoku/

一、DFS深度填數檢測+回溯法

 參考:blog.csdn.net/hll174/article/details/51090461

     1、先把有數字的地方設定標記位為true

     2、 迴圈遍歷陣列中沒有標記位true的地方,也就是需要填數的地方,如果當前為0,即a[i][j]==0,判斷當前所在的九宮格,然後從

          數字1-9依次檢測是否在行、列、宮中唯一滿足唯一的話,則吧數字賦值給a[i][j]=l+1;然後繼續深度遍歷為true的話就返回true,否

          則回溯a[i][j]==0等,不滿足滿足唯一則判斷下一個數字,直到1-9都判斷不滿足則返回false,會回溯到上一層如果當前沒有0,說

         明都已經填滿且符合唯一條件,則返回true;結束

    程式碼:

<pre name="code" class="java">import java.util.Scanner;

public class Shudu {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		while (sc.hasNextInt()) {
			int[][] a = new int[9][9];
			boolean[][] cols = new boolean[9][9];
			boolean[][] rows = new boolean[9][9];
			boolean[][] blocks = new boolean[9][9];// 九大宮的九個數字

			for (int i = 0; i < a.length; i++) {
				for (int j = 0; j < a.length; j++) {
					a[i][j] = sc.nextInt();
					if (a[i][j] != 0) {
						int k = i / 3 * 3 + j / 3;// 劃分九宮格,這裡以行優先,自己也可以列優先
						int val = a[i][j] - 1;
						rows[i][val] = true;
						cols[j][val] = true;
						blocks[k][val] = true;
					}
				}
			}// 資料裝載完畢
			DFS(a, cols, rows, blocks);
			for (int i = 0; i < 9; i++) {
				for (int j = 0; j < 8; j++) {
					System.out.print(a[i][j] + " ");
				}
				System.out.println(a[i][8]);
			}
		}
	}

	public static boolean DFS(int[][] a, boolean[][] cols, boolean[][] rows,
			boolean[][] blocks) {
		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) {
				if (a[i][j] == 0) {
					int k = i / 3 * 3 + j / 3;
					for (int l = 0; l < 9; l++) {
						if (!cols[j][l] && !rows[i][l] && !blocks[k][l]) {// l對於的數字l+1沒有在行列塊中出現
							rows[i][l] = cols[j][l] = blocks[k][l] = true;
							a[i][j] = 1 + l;// 下標加1
							if (DFS(a, cols, rows, blocks))
								return true;// 遞進則返回true
							rows[i][l] = cols[j][l] = blocks[k][l] = false;// 遞進失敗則回溯
							a[i][j] = 0;
						}
					}
					return false;// a[i][j]==0時,l發現都不能填進去
				}// the end of a[i][j]==0
			}
		}
		return true;// 沒有a[i][j]==0,則返回true
	}

}


二、唯一解法

1、 顯性唯一解法

 如果某行已填數字的單元格達到8個,那麼該行剩餘單元格能填的數字就只剩下那個還沒出現過的數字;同理, 如果某列已填數字的單元格達到8個,那麼該列剩餘單元格能填的數字就只剩下那個還沒出現過的數字;如果某九宮格已填數字的單元格達到8個,那麼該九宮格剩餘單元格能填的數字就只剩下那個還沒出現過的數字。

2、 隱唯一解法

顧名思義,隱式唯一候選數法也是唯一候選數法的一種,但它不如顯式唯一候選數法那樣顯而易見。
    由於1-9這9個數字要在每行、每列和每個九宮格內至少出現一次,所以如果某個數字在某行、某列或是某個九宮格內所有單元格的候選數列表中只出現一次,那麼這個數字就應該填入它出現的那個單元格內,並且從該格所在行、所在列和所在九宮格內其它單元格的候選數列表中刪除該數字。 

解題思路:

1、用排除法求取每個值為0的空格所有可能的候選數

2、用唯一解法檢視是否有值為0的空格是的值可以確定;

3、在1,2過程中當某空格是的值可以確定後則將該值從該格所在行、所在列和所在九宮格內其它單元格的候選數列表中刪除該數字;

4、若空格的候選數發生變動時,應檢視候選數是隻在一個,以及對與該格所在行、所在列和所在九宮格內其它單元格的候選數集的影響;

程式碼:

package paichufa;

public class Shudu {

	int[][] sudo = new int[9][9]; // 儲存數獨陣列
	int[][][] may = new int[9][9][9];// may[i][j][k]儲存sudo[i][j]可能的候選數,sudo[i][j]已確定時may[i][j][k]=0

	public static void main(String[] args) {
		// TODO Auto-generated method stub

	}

	private void Way() {

		// 顯式解
		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) {
				if (sudo[i][j] == 0) {
					this.paichu(i, j);
					// System.out.println();

					if (may[i][j][1] == 0) {
						sudo[i][j] = may[i][j][0];
						may[i][j][0] = 0;
						this.delete(i, j, sudo[i][j]);
					}

				}// End if(sudo[i]==0)
			}

		}

		// 隱式解

		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) {
				if (sudo[i][j] == 0) {
					boolean influ=this.bijiao(i, j);
					// System.out.println();
                    if (influ) {  //當獲得某一元素的解後對非同行,同列,同九宮的元素的候選數均有影響
						i=0;
						j=0;
					}
				
				}// End if(sudo[i]==0)
			}

		}

	}// End A

	/**
	 * 
	 * @Description: 
	 *               填充may[i][j][k],並遍歷與sudo[i][j]同行,同列,同九宮元素,獲取sudo[i][j]可能的候選數,填充到may
	 *               [i][j]中
	 * @param @param i
	 * @param @param j
	 * @return void
	 * @throws
	 * @date 2016年9月9日
	 */

	//初始化各空格候選數
	private void paichu(int i, int j) {
		if (may[i][j][0] == 0) {
			for (int k = 1; k < 10; k++) {
				may[i][j][k] = k;
			}
		}

		for (int m = i / 3 * 3; m < i / 3 * 3 + 3; m++) {// 同九宮遍歷
			for (int n = i / 3 * 3; n < i / 3 * 3 + 3; n++) {
				if (sudo[m][n] != 0 && m != i && n != j) {
					int r = 0;
					while (may[i][j][r] != 0) {
						if (may[i][j][r] == sudo[m][n]) {
							may[i][j][r] = 0;
							for (int k = r; k < 8; k++) {
								int temp = may[i][j][k];
								may[i][j][k] = may[i][j][k + 1];
								may[i][j][k + 1] = temp;
							}
						}
						r++;
					}
				}
			}
		}

		// 同行遍歷
		for (int n = 0; n < 9; n++) {
			if (sudo[i][n] != 0 && n != j) {
				int r = 0;
				while (may[i][j][r] != 0) {
					if (may[i][j][r] == sudo[i][n]) {
						may[i][j][r] = 0;
						for (int k = r; k < 8; k++) {
							int temp = may[i][j][k];
							may[i][j][k] = may[i][j][k + 1];
							may[i][j][k + 1] = temp;
						}
					}
					r++;
				}
			}
		}

		// 同列遍歷
		for (int m = 0; m < 9; m++) {
			if (sudo[m][j] != 0 && m != i) {
				int r = 0;
				while (may[i][j][r] != 0) {
					if (may[i][j][r] == sudo[m][j]) {
						may[i][j][r] = 0;
						for (int k = r; k < 8; k++) {
							int temp = may[i][j][k];
							may[i][j][k] = may[i][j][k + 1];
							may[i][j][k + 1] = temp;
						}
					}
					r++;
				}
			}
		}

	}

	/**
	 * 
	 * @Description: 當sudo[i][j]確定時,遍歷與sudo[i][j]同行,同列,同九宮各元素的候選數 若包含在其中,則從該候選數集中刪除,
	 *               並且在刪除後候選數集中僅剩唯一解,則可以確定其解,然後以該元素的行,列,解值為引數進行遞迴呼叫;
	 * @param @param i
	 * @param @param j
	 * @param @param del 要從各候選數集中刪除的值
	 * @return void
	 * @throws
	 * @author 劉林立
	 * @date 2016年9月9日
	 */

	private void delete(int i, int j, int del) {

		// 同九宮遍歷
		for (int m = i / 3 * 3; m < i / 3 * 3 + 3; m++) {
			for (int n = i / 3 * 3; n < i / 3 * 3 + 3; n++) {
				if (may[m][n][0] != 0 && m != i && n != j) {

					for (int r = 0; r < 8 && may[m][n][r] != 0; r++) {
						if (may[m][n][r] == del) {

							may[m][n][r] = 0;
							while (r < 8 && may[m][n][r] < may[m][n][r + 1]) {
								int temp = may[m][n][r];
								may[m][n][r] = may[m][n][r + 1];
								may[m][n][r + 1] = temp;
								r++;
							}

							if (may[m][n][1] == 0) {
								sudo[m][n] = may[m][n][0];
								may[m][n][0] = 0;
								this.delete(m, n, sudo[m][n]);

							}
						}
					}
				}
			}
		}

		// 同行遍歷
		for (int n = 0; n < 9; n++) {
			if (may[i][n][del - 1] != 0 && n != j) {

				for (int r = 0; r < 8 && may[i][n][r] != 0; r++) {
					if (may[i][n][r] == del) {

						may[i][n][r] = 0;
						while (r < 8 && may[i][n][r] < may[i][n][r + 1]) {
							int temp = may[i][n][r];
							may[i][n][r] = may[i][n][r + 1];
							may[i][n][r + 1] = temp;
							r++;
						}

						if (may[i][n][1] == 0) {
							sudo[i][n] = may[i][n][0];
							may[i][n][0] = 0;
							this.delete(i, n, sudo[i][n]);

						}
					}
				}
			}
		}

		// 同列遍歷
		for (int m = 0; m < 9; m++) {
			if (may[m][j][del - 1] != 0 && m != i) {

				for (int r = 0; r < 8 && may[m][j][r] != 0; r++) {
					if (may[m][j][r] == del) {

						may[m][j][r] = 0;
						while (r < 8 && may[m][j][r] < may[m][j][r + 1]) {
							int temp = may[m][j][r];
							may[m][j][r] = may[m][j][r + 1];
							may[m][j][r + 1] = temp;
							r++;
						}

						if (may[m][j][1] == 0) {
							sudo[m][j] = may[m][j][0];
							may[m][j][0] = 0;
							this.delete(m, j, sudo[m][j]);

						}
					}
				}
			}
		}

	}

	/**
	 * 
	 * @Description:將may[i][j]中的所有元素(候選數)依次和與sudo[i][j]同行,同列,同九宮各元素的候選數相比較(在比較時,若sudo[i][j]=0,候選數集不能為空),若某一元素為該候選數集獨有,則該元素即為sudo[i][j]的解;
	 * 
	 * @param @param i
	 * @param @param j
	 * @param @return
	 * @return boolean
	 * @throws
	 * @author 劉林立
	 * @date 2016年9月9日
	 */
	private boolean bijiao(int i, int j) {
		int k = 0;
		int bijiao = may[i][j][k];
		boolean find = true;

		while (may[i][j][k] != 0 && k < 9) {

			// 同九宮遍歷
			for (int m = i / 3 * 3; m < i / 3 * 3 + 3 && find; m++) {
				for (int n = i / 3 * 3; n < i / 3 * 3 + 3 && find; n++) {
					if (may[m][n][0] != 0 && m != i && n != j) {
						for (int r = 0; r < 8 && may[m][n][r] != 0 && find; r++) {
							if (may[m][n][r] == bijiao) {
								find = false;
							}
						}
					}
				}
			}

			if (find) {
				break;
			}

			// 同行遍歷
			for (int n = 0; n < 9 && find; n++) {
				if (may[i][n][0] != 0 && n != j) {
					for (int r = 0; r < 8 && may[i][n][r] != 0 && find; r++) {
						if (may[i][n][r] == bijiao) {
							find = false;
						}
					}
				}
			}

			if (find) {
				break;
			}

			// 同列遍歷
			for (int m = 0; m < 9 && find; m++) {
				if (may[m][j][0] != 0 && m != i) {
					for (int r = 0; r < 8 && may[m][j][r] != 0 && find; r++) {
						if (may[m][j][r] == bijiao) {
							find = false;
						}
					}
				}
			}

			if (find) {
				break;
			}

			k++;
		}

		if (find && may[i][j][k] != 0 && k < 9) {
			sudo[i][j] = may[i][j][k];
			may[i][j][k] = 0;
			this.delete(i, j, sudo[i][j]);

		}

		return find;
	}

}