今天講了N後問題,現在來複習一下。
N後問題就是在N*N格的棋盤上面放置彼此不受攻擊的n個皇后。這裏的皇后指的是國際象棋中的皇后,按照國際象棋的規則,皇后可以攻擊當前行和當前列還有同一斜線的棋子。簡單來說,就是n個皇后的位置不可以在同一行,同一列,同一斜線。因爲這幾天學習的是回溯算法,很簡單的想到了回溯。這個問題也是經典的回溯算法習題之一。
下面來想一下問題所包含的條件,明顯的條件就是棋盤是n*n,而且很簡單就想到n個皇后不可以在同一列,就是每一列都會有一個皇后。下面就是隱式的條件了,同一行和同一列都可以很簡單的解決,同一斜線不是很好想。後來發現同一斜線的皇后都是皇后的位置差和皇后所在的列相等。就是皇后的位置斜率爲1。翻譯爲代碼語言就是,皇后所在的位置的差的絕對值等於皇后的列的差的絕對值。這個後面還會講一下。條件都講完了,下面就說一說算法的主體。
算法的主體是由遞歸構成,遞歸的參數就是當前考慮的皇后的列,爲了下面的計算方便,這裏將皇后的個數和數組都傳了進去。爲什麼傳進去數組?因爲自己一開始想的是一個二維數組,這個也是大家容易想到的,但是後來發現,解的形式和數組的下標有關,乾脆將二維數組變爲了一維數組,將皇后所在的列變爲數組的下標。這樣,就省去了很多的無用功。遞歸的邊界就是k的位置大於等於n。遞歸的主體是一個for循環,將循環的值賦值給數組當前的位置,同時檢驗當前位置是否正確,如果正確就進行下一個遞歸(k的值加一),不正確就進行下一次循環。講到了檢驗k的位置,就說一說這個方法,很簡單,就是判斷當前位置是不是符合問題的條件,要有一個返回值,方便後面的調用。檢驗的時候也是要用到for循環。
算法的主體講完了,下面就直接粘貼代碼,代碼如下:
package sf;
import java.util.Scanner;
//N後問題
public class demo7
{
public static int num=0;//累加和
//判斷當前位置是否正確
public static Boolean judge(int[] p,int k)
{
for(int i=0;i<k;i++)
{
//絕對值的判定和列的判定。(不用判定行)
if((Math.abs(i-k)==Math.abs(p[i]-p[k]))||(p[i]==p[k])) {
return false;
}
}
//要注意這個return的位置,在這裏是爲了讓for循環能夠一直循環下去,不被中間的位置打斷,可以將
//這個return放到for循環中體驗一下。會發現,解的個數會變多。
return true;
}
//算法主體 遞歸回溯 k爲當前考慮位置
public static void Queen(int[] p,int n,int k)
{
//這裏的方法主體還是要多想一想
if(k>=n)
{
num++;
//打印結果
for(int i=0;i<n;i++)
System.out.print(p[i]);
System.out.println("");
}else{
for(int j=1;j<=n;j++)
{
//System.out.println(k);
//將皇后的位置賦值給數組中的元素,相當於放置皇后
p[k]=j;
if(judge(p, k))
{
//遞歸下一個位置
Queen(p, n, k+1);
}
}
}
}
public static void main(String[] args)
{
System.out.println("請輸入皇后的個數:");
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();
int []p=new int[n];
Queen(p,n,0);
//將結果打印出來,如果只是打印這一個計數,程序的運行會快一點。
System.out.println(n+"皇后問題的解共:"+num+"種");
}
}
算法的代碼就是這樣,算法的主體還是不好想的,我想了一會,感覺還是存在着一些問題,但是代碼執行的結果正確,就不在想了。要注意算法中的for循環的指針的大小,這裏還是要想一想的,雖然只有簡單的一維數組。下面就講一講自己的一些想法,這個算法就是簡單的遞歸求解,說的明白一點,就是窮舉法,這就是這個算法的不足之處。只是在簡單的列舉之後,並沒有進行函數限制。這個和回溯的思想有些不一樣,回溯的思想是算法的主體還要有函數限制,將後面的解的一些錯誤的子樹直接去掉,這個算法沒有做到這一步,算法的實現還是有很大的提升空間,大概在求16皇后問題的時候需要100秒就是算法的大概極限了(這個也是在網上看到的),自己還是一個菜鳥,現在就是想一想。還是理解回溯的思想,在窮舉的時候對於結果進行函數限制,方便後面的列舉,大大加快算法的效率。