1. 程式人生 > >Java版數獨演算法實現

Java版數獨演算法實現

數獨的歷史:
  數獨前身為“九宮格”,最早起源於中國。數千年前,我們的祖先就發明了洛書,其特點較之現在的數獨更為複雜,要求縱向、橫向、斜向上的三個數字之和等於15,而非簡單的九個數字不能重複。儒家典籍《易經》中的“九宮圖”也源於此,故稱“洛書九宮圖”。而“九宮”之名也因《易經》在中華文化發展史上的重要地位而儲存、沿用至今。


  1783年,瑞士數學家萊昂哈德·尤拉發明了一種當時稱作“拉丁方塊”(Latin Square)的遊戲,這個遊戲是一個n×n的數字方陣,每一行和每一列都是由不重複的n個數字或者字母組成的。


  19世紀70年代,美國的一家數學邏輯遊戲雜誌《戴爾鉛筆字謎和詞語遊戲》(Dell Puzzle Mαgαzines)開始刊登現在稱為“數獨”的這種遊戲,當時人們稱之為“數字拼圖”(Number Place),在這個時候,9×9的81格數字遊戲才開始成型。

  1984年4月,在日本遊戲雜誌《字謎通訊Nikoil》(《パズル通訊ニコリ》)上出現了“數獨”遊戲,提出了“獨立的數字”的概念,意思就是“這個數字只能出現一次”或者“這個數字必須是惟一的”,並將這個遊戲命名為“數獨”(sudoku)。


實現方法:

import java.util.Random;

public class ShuDu {
	/** 儲存數字的陣列 */
	private static int[][] n = new int[9][9];
	/** 生成隨機數字的源陣列,隨機數字從該陣列中產生 */
	private static int[] num = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

	public static int[][] generateShuDu(){
		// 生成數字
		for (int i = 0; i < 9; i++) {
			// 嘗試填充的數字次數
			int time = 0;
			// 填充數字
			for (int j = 0; j < 9; j++) {
				// 產生數字
				n[i][j] = generateNum(time);
				// 如果返回值為0,則代表卡住,退回處理
				// 退回處理的原則是:如果不是第一列,則先倒退到前一列,否則倒退到前一行的最後一列
				if (n[i][j] == 0) {
					// 不是第一列,則倒退一列
					if (j > 0) {
						j -= 2;
						continue;
					} else {// 是第一列,則倒退到上一行的最後一列
						i--;
						j = 8;
						continue;
					}
				}
				// 填充成功
				if (isCorret(i, j)) {
					// 初始化time,為下一次填充做準備
					time = 0;
				} else { // 繼續填充
					// 次數增加1
					time++;
					// 繼續填充當前格
					j--;
				}
			}
		}
		return n;
	}

	/**
	 * 是否滿足行、列和3X3區域不重複的要求
	 * 
	 * @param row
	 *            行號
	 * @param col
	 *            列號
	 * @return true代表符合要求
	 */
	private static boolean isCorret(int row, int col) {
		return (checkRow(row) & checkLine(col) & checkNine(row, col));
	}

	/**
	 * 檢查行是否符合要求
	 * 
	 * @param row
	 *            檢查的行號
	 * @return true代表符合要求
	 */
	private static boolean checkRow(int row) {
		for (int j = 0; j < 8; j++) {
			if (n[row][j] == 0) {
				continue;
			}
			for (int k = j + 1; k < 9; k++) {
				if (n[row][j] == n[row][k]) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * 檢查列是否符合要求
	 * 
	 * @param col
	 *            檢查的列號
	 * @return true代表符合要求
	 */
	private static boolean checkLine(int col) {
		for (int j = 0; j < 8; j++) {
			if (n[j][col] == 0) {
				continue;
			}
			for (int k = j + 1; k < 9; k++) {
				if (n[j][col] == n[k][col]) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * 檢查3X3區域是否符合要求
	 * 
	 * @param row
	 *            檢查的行號
	 * @param col
	 *            檢查的列號
	 * @return true代表符合要求
	 */
	private static boolean checkNine(int row, int col) {
		// 獲得左上角的座標
		int j = row / 3 * 3;
		int k = col / 3 * 3;
		// 迴圈比較
		for (int i = 0; i < 8; i++) {
			if (n[j + i / 3][k + i % 3] == 0) {
				continue;
			}
			for (int m = i + 1; m < 9; m++) {
				if (n[j + i / 3][k + i % 3] == n[j + m / 3][k + m % 3]) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * 產生1-9之間的隨機數字 規則:生成的隨機數字放置在陣列8-time下標的位置,隨著time的增加,已經嘗試過的數字將不會在取到
	 * 說明:即第一次次是從所有數字中隨機,第二次時從前八個數字中隨機,依次類推, 這樣既保證隨機,也不會再重複取已經不符合要求的數字,提高程式的效率
	 * 這個規則是本演算法的核心
	 * 
	 * @param time
	 *            填充的次數,0代表第一次填充
	 * @return
	 */
	private static Random r=new Random();
	private static int generateNum(int time) {
		// 第一次嘗試時,初始化隨機數字源陣列
		if (time == 0) {
			for (int i = 0; i < 9; i++) {
				num[i] = i + 1;
			}
		}
		// 第10次填充,表明該位置已經卡住,則返回0,由主程式處理退回
		if (time == 9) {
			return 0;
		}
		// 不是第一次填充
		// 生成隨機數字,該數字是陣列的下標,取陣列num中該下標對應的數字為隨機數字
//		int ranNum = (int) (Math.random() * (9 - time));//j2se
		int ranNum=r.nextInt(9 - time);//j2me
		// 把數字放置在陣列倒數第time個位置,
		int temp = num[8 - time];
		num[8 - time] = num[ranNum];
		num[ranNum] = temp;
		// 返回數字
		return num[8 - time];
	}
	
	public static void main(String[] args) {
		int[][] shuDu=generateShuDu();
		// 輸出結果
		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) {
				System.out.print(shuDu[i][j] + " ");
			}
			System.out.println();
		}
	}
}