搜尋演算法入門之N皇后問題
阿新 • • 發佈:2021-01-18
搜尋演算法入門之N皇后問題
題目描述
在國際象棋中,皇后可以攻擊與他在同一條水平線,垂直線,對角線的棋子,現在有一張 N*N 的棋盤,在上面放置 N 個皇后。有多少種使皇后不能互相攻擊的方案?
輸入格式
輸入一個N(N<=15),表示有一個N*N的棋盤.
輸出格式
輸出只有一行,輸出共有多少種可以擺放皇后的方式
樣例輸入
8
樣例輸出
92
解題思路
逐行(列)搜尋。每測試一個位置,就檢查它是否與其他皇后衝突,如果衝突,回溯。
每放置一個皇后,就要記錄所在列,“\”對角線, “/”對角線都不能放置皇后了。
AC 程式碼
深度優先搜尋(DFS)
小白記錄成長,歡迎各路大佬前來教學
#include <iostream>
#include <string>
#include <algorithm>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int n;//n表示這是一個N*N的棋盤
int sum=0;//sum用於計數,記錄可以有多少种放置皇后的方法
bool column[20],cross1[50],cross2[50]; //column用於標記列,cross1用於標記主對角線,cross2用於標記副對角線
int pos[20];//pos是放置棋子的位置
void dfs(int row)
{
//當row與n相同時,說明已經放置到了最後一行,此時計數器++,並且回溯返回上一層
if(row==n)
{
sum++;
return ;
}
for(int i=0; i<n; i++)
{
if(!(column[i] || cross1[row-i+n] || cross2[row+i]))//判斷是否可以放置皇后
{
//對皇后已經控制的行和列進行標記
column[i]=cross1[row-i+n]=cross2[row+i]=true;
pos[row]=i;
dfs(row+1);
//解除標記,這樣才能在新位置上放置皇后
column[i]=cross1[row-i+n]=cross2[row+i]=false;
}
}
}
int main()
{
cin>>n;
//首先將列,主對角線,副對角線的標記全部標位false,表示起始狀態下所有位置都沒有放置皇后
memset(column,false,sizeof(column));
memset(cross1,false,sizeof(cross1));
memset(cross2,false,sizeof(cross2));
dfs(0);
cout<<sum<<endl;
return 0;
}
狀態壓縮
把狀態放在一個4位元組的int整型變數中,並且使用位運算————這樣,速度會比普通演算法的快很多
#include <iostream>
#include <string>
#include <algorithm>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
//sum用來記錄皇后放置成功的不同佈局數;upperlim用來標記所有列都已經放置好了皇后
int n, sum=0, upperlim=1;
void test(long row ,long ld,long rd)
{
if(row != upperlim)
{
/*
row,ld,rd進行“或”運算,求得所有可以放置皇后的列,對應位位0.
然後再取反後“與”上全1的數,來求得當前所有可以放置皇后的位置,對應列改為1.
也就是求取當前哪些列可以放置皇后。
*/
long pos=upperlim & ~(row | ld | rd);
while(pos)// 0 -- 皇后沒有位置可放,直接回溯
{
/*
拷貝 pos 最右邊的 1 為 bit ,其餘 bit 置為 0
也就是取得可以放皇后的最右邊的列
*/
long p = pos & -pos;
/*
將 pos 最右邊的 1 的 bit 清零。
也就是為獲取下一次的最右可用列使用作準備
程式將來會回溯到這個位置繼續試探。
*/
pos -= p;
/*
row + p , 將當前列置為 1 ,表示記錄這次皇后放置的列
(ld + p) << 1, 表示當前皇后左邊相鄰的列不允許下一個皇后放置
(ld + p) >> 1, 表示當前皇后右邊相鄰的列不允許下一個皇后放置
*/
test(row + p, ( ld + p ) << 1 ,( ld + p )>>1 );
/*
此處的位移操作實際上是記錄對角線上的限制,只是因為問題都歸化到
一行網格上來解決,所以表示為列的限制就可以了。顯然。隨著移位在
每次選擇列之前進行,原來 N * N 網格中某個已放置的皇后鎮對其對角
線上產生的限制都被記錄了下來
*/
}
}
else
sum++; // row 的所有位都為1,即找到了一個成功的佈局,回溯。
}
int main()
{
cin>>n;
// n 皇后只需要 N 位儲存,N列中某列有皇后則對應的 bit 置為 1
upperlim = (upperlim << n+1) - 1;
test(0,0,0);
cout<<sum<<endl;
return 0;
}