N皇后問題(各種優化)
0.問題引入
N皇后問題是一個經典的問題,在一個N*N的棋盤上放置N個皇后,每行一個並使其不能互相攻擊(同一行、同一列、同一斜線上的皇后都會自動攻擊),問有多少種擺法。
題目連結:https://www.luogu.org/problemnew/show/P1219
1、普通回溯
深搜+回溯,它是一種系統地(能找到全部的解)搜尋問題的解的方法。
基本思想是:從一條路往前走,能進則進,不能進則退回來,換一條路再試。
演算法思想:
1. 在第k(1≤k≤N)行選擇一個位置,判斷這個位置是否可以擺,可以擺就進入第 k+1 行,不可以就試本行的下一個位置是否可以擺;
2. 如果一直試到本行最後一個都不能擺,說明前面k-1行有位置選得不恰當,回到第 k-1 行,試 k-1 行的下一個位置。
3. 反覆執行1,2,到最後一行擺上棋子時,說明找到了一個解。
一個問題能用回溯法求解,它的解具有 N N N元組的形式,迷宮問題中就是到終點的 N N N 個座標,N皇后問題中就是 N N N 個皇后所處的(不同行)列號。
遞迴實現:
#include <iostream>
#include <math.h>
using namespace std;
int x[15];//x[k]:第k行上第x[k]個位置擺上了皇后
int N, cnt;
//row,col表示當前嘗試擺放皇后的行號與列好
bool check(int row, int col) {
//回溯,不會受到後面行的影響
for (int i = 1; i < row; i++) {
if (x[i] == col)//列衝突
return false;
if (abs(row - i) == abs(col - x[i]))//對角線衝突
return false;
}
return true;
}
void DFS(int k) {
if (k == N + 1) {
cnt++;
if (cnt <= 3) {
for (int i = 1; i <= N; i++) {
cout << x[i] << " ";
}
cout << endl;
}
return;
}
for (int i = 1; i <= N; i++) {
//如果當前位置合法
if (check(k, i)) {
x[k] = i;
DFS(k + 1);
//本問題沒有明顯的回溯操作,因為下一個位置的賦值會將上一次賦值覆蓋掉
}
}
}
int main() {
cin >> N;
DFS(1);
cout << cnt << endl;
return 0;
}
非遞迴實現:
#include <iostream>
#include <math.h>
using namespace std;
int x[15];
int N, cnt;
bool check(int row, int col) {
//回溯,不會受到後面行的影響
for (int i = 1; i < row; i++) {
if (x[i] == col)return false;
if (abs(row - i) == abs(col - x[i]))return false;
}
return true;
}
void queen(){
//i表示第幾冊,j表示在第i層搜尋位置
int i = 1, j = 1;
while (i <= N){
while (j <= N){
//如果當前位置合法
if (check(i, j)) {
//把x[i]暫時賦值成j
x[i] = j;
j = 1;
break;
}
else
j++;
}
//第i行沒有找到可以放置皇后的位置
if (x[i] == 0){
//如果回溯到了第0行,說明搜尋結束
if (i == 1)
break;
//回溯
else{
i--;
j = x[i] + 1;//j為上一行的皇后位置+1
x[i] = 0;//上一行清零
continue;
}
}
//如果找到了第N層,輸出出來
if (i == N){
cnt++;
if (cnt <= 3) {
for (int i = 1; i <= N; i++) {
cout << x[i] << " ";
}
cout << endl;
}
j = x[i] + 1;
x[i] = 0;
continue;
}
i++;
}
}
int main() {
cin >> N;
//DFS(1);
queen();
cout << cnt << endl;
return 0;
}
均無法通過最後一個測試點
2、減半優化
其實不難想象,因為棋盤的對稱,每個結果總有另一個與之對稱。只用回溯一半 ,效率能提升50%左右。
方法就是第一行只搜尋該行的前一半的位置即可(只搜一半的行也是可以的,只是這樣更方便)。但是對於奇數的N,計算出來的結果會將第一行中間位置的解算了兩遍。所以要單獨處理一下。
#include <iostream>
#include <vector>
#include <math.h>
using namespace std;
int x[15];
vector<int> v[3];//處理解數量不足3個的情況
int N, cnt;
int flag, oddCnt;
bool check(int row, int col) {
//回溯,不會受到後面行的影響
for (int i = 1; i < row; i++) {
if (x[i] == col)return false;
if (abs(row - i) == abs(col - x[i]))return false;
}
return true;
}
void DFS(int k) {
if (k == N + 1) {
if (flag&&x[1] == (N + 1) / 2) {
oddCnt++;
if (oddCnt % 2 == 0)cnt++;
}
else
cnt++;
if (cnt <= 3) {
for (int i = 1; i <= N; i++) {
cout << x[i] << " ";
v[cnt - 1].push_back(x[i]);
}
cout << endl;
}
return;
}
int len = (k == 1) ? (N + flag) / 2 : N;
for (int i = 1; i <= len; i++) {
if (check(k, i)) {
x[k] = i;
DFS(k + 1);
}
}
}
int main() {
cin >> N;
if (N & 1)flag = 1;
DFS(1);
for (int i = cnt, j = cnt - 1; i < 3 && j >= 0; i++, j--) {
for (int k = N - 1; k >= 0; k--) {
cout << v[j][k] << " ";
}
cout << endl;
}
cout << cnt*2 << endl;
return 0;
}
勉強跑過了
3、優化判斷
以本圖為例:
每條橙色對角線的行列之差是相同的。
每條藍色對角線的行列之和是相同的。
用兩個bool陣列用來記錄藍色、橙色斜線是否已經被佔據。考慮到行列之差可能為負數,棋盤座標 [x,y] 對應下標 [ x - y + n ]。
這裡下標不能用 a b s ( x − y ) abs(x-y) abs(x−y) 的原因是主對角線邊上的兩條對角線,他們的 a b s ( x − y ) abs(x-y) abs(x−y) 都是 1 1 1
再用一個陣列記錄第 i 列是否有元素。
#include <iostream>
using namespace std;
int N, cnt, a[15];
//正斜線、反斜線、行
bool x1[31], x2[31], y[15];
void DFS(int k) {
if (k == N + 1) {
cnt++;
if (cnt <= 3) {
for (int i = 1; i <= N; i++) {
cout << a[i] << " ";
}
cout << endl;
}
return;
}
for (int i = 1; i <= N; i++) {
//這裡x2下標不能用abs(k-i),那樣是不對的,因為主對角線邊上的兩條對角線,他們的abs(k-i)都是1
if (!x1[i + k] && !x2[k - i + N] && !y[i]) {
a[k] = i;
x1[i + k] = true;
x2[k - i + N] = true;
y[i] = true;
DFS(k + 1);
x1[i + k] = false;
x2[k - i + N] = false;
y[i] = false;
}
}
}
int main() {
cin >> N;
DFS(1);
cout << cnt << endl;
return 0;
}
當N較大時,演算法會耗費大量的次數在無用的回溯上,時間還是沒有顯著提高。
4、位運算優化
警告:以下程式碼可能引起不適,建議60歲以下使用者在家長陪同下閱讀。
位運算是計算機最快的操作,我們可以用數的二進位制位表示各縱列、對角線是否可以放置皇后。
參考:https://blog.csdn.net/Hackbuteer1/article/details/6657109
#include <iostream>
#include <queue>
using namespace std;
int n, limit, cnt;
int x[15], k = 1;
//行,左對角線,右對角線
void DFS(int row,int left,int right) {
if (row != limit) {
//row|left|right表示這一行的所有禁止位置,取反再和limit按位與,得到的是該行可以放的幾個位置
int pos = limit & ~(row | left | right);
//每一個可以擺的位置,都要做一次
while (pos) {
//找到的可以放皇后的位置(pos二進位制最右邊的一個1)
int p = pos & -pos;// pos & (~pos+1);
//把這一位置0,表示不為空
pos &= pos - 1;//pos=pos-p;
//把p所在row,left,right的位都置1。
//(left | p)<< 1 是因為這一行由左對角線造成的禁止位在下一行要右移一下;right同理
DFS(row | p, (left | p) << 1, (right | p) >> 1);
}
}
else {
cnt++;
}
}
int main() {
cin >> n;
limit = (1 << n) - 1;
DFS(0, 0, 0);
cout << cnt << endl;
return 0;
}
#include <iostream>
#include <queue>
using namespace std;
int n, limit, cnt;
int x[15], k = 1;
//行,左對角線,右對角線
void DFS(int row,int left,int right) {
if (row != limit) {
int pos = limit & ~(row | left | right);
while (pos) {
//找到的可以放皇后的位置
int p = pos & -pos;// pos & (~pos+1);
pos &= pos - 1;
if (cnt < 3) {
int t = p, num = 1;
while (t != 1) {
num++;
t >>= 1;
}
x[k++] = num;
}
DFS(row | p, (left | p) << 1, (right | p) >> 1);
if (cnt < 3) k--;
}
}
else {
if (cnt < 3) {
for (int i = 1; i <= n; i++) {
cout << x[i] << " ";
}
cout << endl;
}
cnt++;
}
}
int main() {
cin >> n;
limit = (1 << n) - 1;
DFS(0, 0, 0);
cout << cnt << endl;
return 0;
}
效率提升顯著
相關文章
- N皇后問題
- sql優化講課中引出的各種問題!SQL優化
- 回溯法(排列樹)解決八(N)皇后問題
- 經典n皇后問題java程式碼實現Java
- 用棧+回溯+非遞迴解決N皇后問題遞迴
- 使用回溯演算法解決N皇后問題以及間隔排列問題演算法
- leetcode演算法題解(Java版)-9-N皇后問題LeetCode演算法Java
- Leetcode每日一題:52.N-Queens II(N皇后Ⅱ)LeetCode每日一題
- 藍橋杯-N皇后
- 多執行緒解n王后問題的優化執行緒優化
- SQL優化--用各種hints優化一條SQLSQL優化
- 洛谷八皇后問題
- 演算法:N皇后二演算法
- javascript中的各種問題JavaScript
- RMQ問題的各種解法MQ
- 利用C++多執行緒優化n王后問題C++執行緒優化
- 八皇后問題python解法Python
- 八皇后問題自我總結
- 【演算法】8皇后問題演算法
- ArchLinux各種問題彙總Linux
- 八皇后問題分析和實現
- LeetCode 52. N皇后 IILeetCode
- 優思學院|精益生產的各種問題和解決方案
- 凸優化問題優化
- 效能優化問題優化
- 被騰訊問蒙的各種Redis複雜問題Redis
- 【調優】設計問題還是優化問題?優化
- hadoop啟動遇到的各種問題Hadoop
- 各種元標籤SEO優化的HTML模板大全優化HTML
- 從八皇后問題到回溯演算法演算法
- 演算法學習回顧-皇后問題演算法
- js解八皇后問題程式碼例項JS
- 面試可能會遇到的各種問題講解面試
- 資料結構連結串列各種問題資料結構
- 斜率優化(凸包優化)DP問題acm優化ACM
- 前端效能優化(四)——網頁載入更快的N種方式前端優化網頁
- 重構 - 用各種方式優化自己的函式庫優化函式
- 八皇后問題的錯誤程式碼示範