問題描述:
N皇后問題是把N個皇后放到的棋盤中,使它們不會相互攻擊。根據國際象棋規定,皇后可以喫掉和它同行、同列或同一斜線上的任意一個棋子。設計算法給出所有解。
如下圖:如果一個皇后在圖中的位置,那麼,在她的行,列,對角線上都不能有其他皇后
算法分析:
解決這個問題最簡單粗暴的方法就是枚舉,枚舉所有的可能性,最後得出解,如:求 4 皇后問題時,我們可以直接使用 4 層循環,枚舉所有的可能性。雖然這樣算法的時間複雜度非常高,但是簡單粗暴啊。但是呢,我們這裏要求 N 皇后問題,皇后的個數是未知的,我們就不能通過寫多層循環的方法來解決。那該怎麼辦呢?
我們可以採用遞歸,使用遞歸來代替多層的循環。
核心算法詳解:
我們設定一個數組 queenPos[M]
,用來存放對於N皇后每一種方案的結果。比如:queenPos[0]=3
,就表示第 0 行的皇后放在第 3 列上。
寫一個函數NQueen(int k)
,它表示,在第 0 行到第 行的皇后都已經擺好的情況下,我們開始擺放第 行的皇后,直到把 個皇后都擺完。
- 在主函數中,我們從擺放第 0 行的皇后開始。即:
NQueen(0);
- 遞歸出口:
k == N
,表示 N 個皇后已經擺好。爲什麼要queenPos[i] + 1
呢?當然是爲了程序的友好性了,只有計算機行業的人,數東西的時候纔是從 0 開始的。 - 如果
k != N
,我們就開始擺放第 k 行的皇后以及後面的皇后。- 😊逐個嘗試第 k 個皇后擺放的位置,一共有 N 個位置,所以循環從 0 到 N-1。
- 😊對每個位置進行判斷,看它是否和前面擺放好的皇后有衝突。衝突有兩種情況:
①. 當該皇后要擺放的位置的同一列中已經有了其他的皇后。即:queenPos[j] == i
②. 當該皇后要擺放的位置的對角線上已經有了其他的皇后。即:abs(queenPos[j] - i) == abs(k - j)
。(爲什麼是這樣判斷呢,大家畫個圖,一目瞭然)
③. 😎當這兩種衝突滿足其一時,該位置就不能擺放皇后。則測試下一個位置。 - 如果檢測到某個位置並不和前面已經擺放好的皇后有衝突,即:
if (j == k)
;則在該位置擺放第 k 個皇后,即:queenPos[k] = i;
,表示將第 k 個皇后擺放在位置 i ;後繼續擺放第 k+1 個皇后,即:NQueen(k + 1);
- 爲什麼我們只調用了
NQueen(0);
,它最後會輸入所有的解呢?
1. 因爲我們對每一行的皇后的所有可能的位置我們都去進行了檢測。
2. 它歸根到底就是一個 N 重循環。
程序比較複雜,我們舉個例子,看一個它的執行過程。
例:現在要求 4 皇后的問題:
- 執行
NQueen(0);
,在第 0 行擺放第0個皇后。進入循環for (i = 0; i < N; i++)
,顯然,這時前面並沒有擺放其他的皇后,所以這個第 0 個皇后就擺放在第 0 行第 0 列的位置,即:queenPos[k] = i;
,進而繼續擺放第一個皇后,NQueen(k + 1);
。
- 在第一行擺放第 1 個皇后。在該行中尋找和第 0 個皇后不衝突的位置,最終將該皇后放入該行第 2 列的位置。然後繼續擺放下一個皇后。
- 擺放第二個皇后(從0開始)。在第 2 行尋找與前面兩個皇后不發生衝突的位置。我們發現,這時,不論將該皇后放入該行的哪個位置,都會與前面的兩個皇后發生衝突。這時,我們返回重新擺放第 0 個皇后。
- 重新擺放第 0 個皇后,這時,我們已經得知不能將第 0 個皇后放進第 0 行第 0 列的位置。所以,我們將該皇后放在第 0 行第 1 列的位置。然後重複第二步和第三步,直到 4 個皇后全部放完。
- 經過多次嘗試與檢測,我們得到一種方案:
- 我們繼續尋找另外一種方案。直到沒有滿足條件的方案,程序結束。
C++實現:
#include<iostream>
using namespace std;
const int M = 100;
int N;
int queenPos[M];//存放皇后的擺放位置
int sum = 0;//記一共有多少種解決方案
void display()//用來圖形化輸出結果,@表示皇后
{
int i, j;
int k;
cout << endl;
sum++;
for (i = 0; i < N; i++)
{
cout << " ";
for (k = 0; k < N; k++)
{
cout << "---";
}
cout << endl;
for (j = 0; j < N; j++)
{
if (j == queenPos[i])
{
cout << "| ";
cout << "@";
}
else
{
cout << "| ";
cout << ".";
}
}
cout << " |"<<endl;
}
cout << " ";
for (i = 0; i < N; i++)
{
cout << "---";
}
cout << "\n"<<endl;
}
void NQueen(int k)
{
int i;
if (k == N)//N個皇后已經全部擺好
{
cout << N << "皇后的擺放位置是:";
for (i = 0; i < N; i++)
{
cout << queenPos[i] + 1 << " ";
}
cout << endl;
cout << "圖解如下:" << endl;
display();
return;
}
for (i = 0; i < N; i++)//在一行中逐個檢測每個位置
{
int j;
for (j = 0; j < k; j++)//和語句擺好的前幾個皇后進行衝突檢測
{
if (queenPos[j] == i || abs(queenPos[j] - i) == abs(k - j))
{
break;//發生衝突,則檢測下一個位置
}
}
if (j == k)//該位置不與前面的皇后發生衝突
{
queenPos[k] = i;//將第k個皇后放在第i的位置上
NQueen(k + 1);
}
}
}
int main()
{
cin >> N;
NQueen(0);//擺放第0個皇后
cout <<N<<" 皇后的解決方案有 "<< sum << " 種"<<endl;;
return 0;
}
輸出:
Java實現:
package lu;
import java.util.Scanner;
public class Demo2 {
public static int N;
public static int sum=0;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int []queenPos=new int[100];
N=in.nextInt();
NQueen(0,queenPos);
System.out.println(N+" 皇后的解決方案有 "+sum+" 種");
}
public static void NQueen(int k,int []queenPos)
{
int i;
if (k == N)
{
System.out.print(N+"皇后的擺放位置是:");
for (i = 0; i < N; i++)
{
int a=queenPos[i] + 1;
System.out.print(a+" ");
}
System.out.println();
System.out.println("圖解如下:");
display(queenPos);
return;
}
for (i = 0; i < N; i++)
{
int j;
for (j = 0; j < k; j++)
{
if (queenPos[j] == i || Math.abs(queenPos[j] - i) == Math.abs(k - j))
{
break;
}
}
if (j == k)
{
queenPos[k] = i;
NQueen(k + 1,queenPos);
}
}
}
public static void display(int []queenPos)
{
int i, j;
int k;
System.out.println();
sum++;
for (i = 0; i < N; i++)
{
System.out.print(" ");
for (k = 0; k < N; k++)
{
System.out.print("---");
}
System.out.println();
for (j = 0; j < N; j++)
{
if (j == queenPos[i])
{
System.out.print("| "+"@");
}
else
{
System.out.print("| "+".");
}
}
System.out.println(" |");
}
System.out.print(" ");
for (i = 0; i < N; i++)
{
System.out.print("---");
}
System.out.println();
}
}
輸出:
更多有趣算法:
葉天帝爲天下蒼生血戰黑暗至尊(又名0-1揹包問題)
葉天帝對戰黑暗至尊,多次瀕臨死境,卻創出 分治算法(分治算法解決歸併排序),小白也能看懂的歸併
兩行代碼帶你體驗算法極致的美
深入理解動態規劃