【藍橋杯】2n皇后問題Java解析(深度優先搜索+回溯)。

2n皇后問題題目描述 :

給定一個n*n的棋盤,棋盤中有一些位置不能放皇后。現在要向棋盤中放入n個黑皇后和n個白皇后,使任意的兩個黑皇后都不在同一行、同一列或同一條對角線上,任意的兩個白皇后都不在同一行、同一列或同一條對角線上 。問總共有多少种放法?n小於等於8。

輸入格式 :

輸入的第一行爲一個整數n,表示棋盤的大小。接下來n行,每行n個0或1的整數,如果一個整數爲1,表示對應的位置可以放皇后,如果一個整數爲0,表示對應的位置不可以放皇后。

樣例輸入 :

4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
輸出 : 2

問題分析:在分析 2n皇后 問題前,我們先來看看此問題的另一個版本: n皇后問題

顧名思義,所謂的2n皇后問題,無非在n皇后的基礎上增加了n個另一種顏色皇后。且兩種顏色皇后互不影響。所以n皇后問題只要有n個同色皇后滿足條件便可。

上圖 :
在這裏插入圖片描述
現在我們可以設想:同樣大小的棋盤,如果解得全部n皇后問題的解集,那麼這些解集中只要有兩種放法中皇后落子位置沒有重複,那麼就可以得到兩種2n皇后的解法(顏色互換有兩種放法)。
所以說,只要解決了n皇后問題,那麼2n皇后問題便迎刃而解,且能去除多放n個皇后的運算和判斷

N皇后問題

分析:
n*n規格的棋盤要放下n個皇后,那麼說明每一行必須且僅能落下一個皇后。
這時候我們便能使用深度優先搜索算法去組合出所有的排列方式,並對每一步落子位置加以判斷。
使其滿足規則。
深度優先搜索算法 的本質便是是遞歸。遞歸函數部分代碼如下:

	static public void dfs(int line,int[][] temp) {   //該次搜索行數,輸入數據二維數組
		if(line<n) {                                  //遞歸函數出口
			for(int i=0;i<n;i++) {
				if(temp[line][i]==1&&check(line,i)) { //檢查該位置是否能落子,並判斷落子後是否滿足規則
					flag[line][i] = 1;					//flag數組表示棋盤落子位置,1代表皇后
					dfs(line+1,temp);				 //進行下一行搜索
					flag[line][i]=0;					//回溯
				}
			}
		}else {
			StringBuilder sb = new StringBuilder();    //得到一種解法,將其位置信息以字符串形式保存在
			for(int i=0;i<n;i++) {					  //list集合中。
				for(int j=0;j<n;j++) {
					if(flag[i][j]==1)
						sb.append(""+j);
				}
			}
			list.add(sb.toString());
		}
	}

這樣便能得到了所有的n皇后問題的解集,並將其位置信息保存在了ArrayList<string list集合中。(泛型<>裏打的String爲什麼會消失?)
然後我們去兩兩對比解集中的元素,只要每一位字符都不相同。便能組合成兩種2n皇后問題的解法。

比較函數代碼如下:

	for(int i=0;i<list.size();i++) {
			for(int j=i+1;j<list.size();j++) {
				if(eqs(list.get(i),list.get(j)))
					num+=2;
			}
		}
	static public boolean eqs(String s1,String s2) {
		for(int i=0;i<s1.length();i++) {
			if(s1.charAt(i)==s2.charAt(i))
				return false;
		}
		return true;	
	}

解題完整代碼:

import java.util.ArrayList;
import java.util.Scanner;

public class the2n皇后問題 {
	static int n;
	static int[][] flag; 
	static ArrayList<String> list = null;
	public static void main(String[] args) {
		Scanner s = new Scanner(System.in);
		n = s.nextInt();
		flag = new int[n][n];        //落子標記數組
		int[][] temp = new int[n][n];
		for(int i=0;i<n;i++) {
			for(int j=0;j<n;j++) {
				temp[i][j]= s.nextInt();
			}
		}
		list = new ArrayList<String>();
		dfs(0,temp);
		int num = 0;
		for(int i=0;i<list.size();i++) {
			for(int j=i+1;j<list.size();j++) {
				if(eqs(list.get(i),list.get(j)))
					num+=2;
			}
		}	
		System.out.println(num);
	}
	
	static public void dfs(int line,int[][] temp) {   //該次搜索行數,輸入數據二維數組
		if(line<n) {                                  //遞歸函數出口
			for(int i=0;i<n;i++) {
				if(temp[line][i]==1&&check(line,i)) { //檢查該位置是否能落子,並判斷落子後是否滿足規則
					flag[line][i] = 1;					//flag數組表示棋盤落子位置,1代表皇后
					dfs(line+1,temp);				 //進行下一行搜索
					flag[line][i]=0;					//回溯
				}
			}
		}else {
			StringBuilder sb = new StringBuilder();    //得到一種解法,將其位置信息以字符串形式保存在
			for(int i=0;i<n;i++) {					  //list集合中。
				for(int j=0;j<n;j++) {
					if(flag[i][j]==1)
						sb.append(""+j);
				}
			}
			list.add(sb.toString());
		}
	}
	
    public static boolean check(int line ,int row) {
    	int l = line; int r = row;
    	while(--l>=0&&--r>=0) {    //左上對角線檢查
    		if(flag[l][r]==1)
    			return false;
    	}
    	   l = line; r = row;
    	while(--l>=0&&++r<n) {		//右上對角線檢查
        	if(flag[l][r]==1)
        		return false;
       	}    
    	while(--line>=0) {
    		if(flag[line][row]==1)   //上方檢查
    			return false;
    	}
		return true;
    }
    
	static public boolean eqs(String s1,String s2) {
		for(int i=0;i<s1.length();i++) {
			if(s1.charAt(i)==s2.charAt(i))
				return false;
		}
		return true;	
	}
}

運行結果截圖 :
在這裏插入圖片描述
歡迎大家提出改進意見,互相交流。
有疑問可留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章