算法報告之Nqueen
1 問題重述
- 回溯法
- 全排列法
- 棧求解
- 位運算優化
- 分支界限法
2 回溯法
2.1分析
按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術爲回溯法。
我們可以用回溯法解決N皇后問題,用數組模擬棋盤,從第一行開始,依次選擇位置, 如果當前位置滿足條件,則向下選位置,如果不滿足條件,那麼當前位置後移一位。
2.2步驟
- 用數組存儲皇后的位置,將i設置爲0
Int place(*x,n)函數用來判斷皇后是否被攻擊,其中數組x[]用來表示列數,n爲皇后個數,判斷的條件是(x[i]-x[n]==i-n‖x[i]-x[n]==n-i‖x[i]==x[n]); - 初始化打印函數,相當於對棋盤初始化。將可以放皇后的位置記爲1,不放的位置記爲0;
- 在子函數Back()中打印皇后解的空間;
- 求解過程中,如果滿足一組可行解,則sum++。Int i=0,若x[i]>=n進行下一行,i++;當i=n時,sum++;輸出該組可行解個數和位置矩陣,i–,回溯到上一層繼續搜索可行解。
2.3代碼實現
#include<bits/stdc++.h>
using namespace std;
static int n,sum=0; //可行解個數
static int pos[20];
int place(int k)
{
//判斷是否在一條線上,並返回0,1
for(int i=1;i<k;i++){
if(pos[i]==pos[k]||(i+pos[i])==(pos[k]+k)||pos[i]-i==pos[k]-k)
return 0;
}
return 1;
}
void Back(int m)
{
if(m>n){
sum++;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(j<pos[i]||j>pos[i])
cout<<"*";
else
cout<<"Q";//如果已經安排完畢則輸出棋盤和記錄
}
cout<<endl;
}
printf("第%d種解法如上圖所示: ",sum);
for(int i=1;i<=n;i++)
cout<<pos[i]<<" ";
cout<<endl<<endl<<endl;
}
else
{
//如果沒有安排完則遞歸繼續下一個安排,無解則返回上一個
for(int i=1;i<=n;i++)
{
pos[m]=i;
if(place(m))
Back(m+1);
}
}
}
int main()
{
printf("請輸入皇后數量N:");
cin>>n;
printf("\n(Q表示皇后,*表示棋盤)\n\n\n");
Back(1);
printf("%d個皇后共有以上%d種解法\n\n\n",n,sum);
}
2.4 運行結果
3 全排列法
3.1分析
這次用全排列來求了,橫豎都不能相同,那麼就是n個元素全排列的問題,然後從中找出對角線上也不符合的,剩下的就都符合了.
自己想了一個全排列的算法,不知道還有沒有更高效的.原理是插入算法,每次插入,右邊的元素右移,左邊的不變,這個可以用過改下標數組來實現.然後,由於下標與元素的一一對應,元素的排列就等於下標的排列,不用再倒換過來了.
現在8皇后問題,考慮到對稱性,可以下降到15毫秒了
3.2代碼實現
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define maxsize 20+7
using namespace std;
int queen[maxsize];
int n;
int count_=0;
int main()
{
printf("請輸入皇后數量N:");
scanf("%d",&n);
cout<<endl;
for(int i=0;i<n;i++)
{
// Init array
queen[i]=i;
}
do
{
// for(int i=0;i<n;i++)
// printf("%d\t",queen[i]);
// printf("\n");
// generate the permutation of queen
bool find=true;
for(int i=0;i<n;i++)
{
for(int j=i+1;j<n;j++)
{
if(j-i==abs(queen[j]-queen[i]))
{
// cout<< "false!!! " <<endl;
find=false;
break;
}
if(!find)
break;
}
}
// printf("\n");
if(find)
{
count_++;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(j<queen[i]||j>queen[i])
cout<<"*";
else
cout<<"Q";
}
cout<<endl;
}
printf("第%d種解法 ",count_);
// for(int i=0;i<n;i++)
// cout<<queen[i]<<" ";
cout<<endl<<endl<<endl;
}
}while(next_permutation(queen,queen+n));
printf("count:%d",count_);
return 0;
}
3.3運行結果
4 棧求解
4.1代碼實現
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#define MaxSize 100000+7
#define maxsize 20+7
using namespace std;
int path[maxsize][maxsize];
int n;
int y_pos[maxsize];
int count=0;
bool judge(int num)
{
for(int i=0;i<num;i++)
{
if(y_pos[i]==y_pos[num] || abs(y_pos[num]-y_pos[i])==num-i)
return false;
}
return true;
}
typedef struct
{
int x;
int y;
int di;
}Box;
typedef struct
{
Box data[MaxSize];
int top;
}StType;
void InitStack(StType *&s)
{
s=(StType *)malloc(sizeof(StType));
s->top=-1;
}
void DestroyStack(StType *&s)
{
free(s);
}
bool GetTop(StType *&s,Box &e)
{
if(s->top==-1)
return false;
e=s->data[s->top];
return true;
}
bool push(StType *&s,Box e)
{
if(s->top==MaxSize-1)
return false;
s->top++;
s->data[s->top]=e;
return true;
}
bool pop(StType *&s,Box &e)
{
if(s->top==-1)
return false;
e=s->data[s->top];
s->top--;
return true;
}
4.2運行結果
5 位運算優化
5.1代碼實現
#include<bits/stdc++.h>
using namespace std;
int board;
int n;
int ans = 0;
void n_queen(int col,int ld,int rd){
if(col == board){
ans++;
return;
}
int pos = board & (~(col | ld | rd));
while(pos){
int p = pos & (-pos);
pos = pos - p;
n_queen(col | p , (ld | p) << 1,(rd | p) >> 1);
}
}
int main(){
printf("請輸入皇后數量N:");
cin>>n;
board = (1 << n) - 1;
clock_t ts=clock();
n_queen(0,0,0);
cout<<ans<<endl;
clock_t te=clock();
cout<<"共用時"<<te-ts<<"毫秒"<<endl;
}
5.2運行結果
6 分支界限法
6.1代碼
#include<iostream>
#include<ctime>
using namespace std;
int count=0;
bool isOK(int n, int pieces[])
{ //剪枝函數
//判斷當前狀態是否合理,即皇后會不會互相攻擊
for (int i = 1; i <= n-1; i++)
{
for (int j = i + 1; j <= n; j++)
{
int left = -(j - i);//向左的斜線
int right = (j - i);//向右的斜線
if (pieces[j] == pieces[i] + left||pieces[j] == pieces[i] + right)
{//第i行皇后和第j行皇后會互相攻擊
return false;
}
}
}
//所有皇后都不會互相攻擊
return true;
}
void swap(int &a, int &b)
{
int t = a;
a = b;
b = t;
}
void nQueen(int n, int t, int pieces[])
{
if (t > n)
{
count++;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j < pieces[i]; j++)
cout << "* ";
cout << "Q"<<" ";
// cout << pieces[i]<<" ";
for (int j = pieces[i] + 1; j <= n; j++)
cout << "* ";
cout << endl;
}
cout << endl;
}
else
{
for (int i = t; i <= n; i++)
{
swap(pieces[t], pieces[i]);
if (isOK(t, pieces))
{
nQueen(n, t + 1, pieces);
}
swap(pieces[t], pieces[i]);
}
}
}
int main()
{
int n;
printf("請輸入皇后數量N:");
cin >> n;
int *pieces = new int[n + 1];
for (int i = 1; i <= n; i++)
{
pieces[i] = i;
}
clock_t ts=clock();
nQueen(n, 1, pieces);
cout<<"count:";
cout<<count<<endl;
clock_t te=clock();
cout<<"共用時"<<te-ts<<"毫秒"<<endl;
return 0;
}
6.2運行結果
7 總結
效率比較(N=8時)
算法 | 運行時間 |
---|---|
遞歸回溯 | 629ms |
全排列 | 561ms |
棧 | 730ms |
位運算 | 10ms |
分支界限法 | 705ms |
從以上實驗結果可以看出,在同樣使用回溯法的情況下,基於位運算的解法大大地提高了求解速度,而且隨着N值越大,求解速度提高的越多。
研究了兩天各類八皇后寫法,感覺自己對位運算的運用加深了一個層次,而且算法的設計如果能夠遵循計算機的運行方式,將會獲得更高的效率,更大的收穫還是知道了自己的渺小,一個原以爲十分簡單的八皇后都可以衍生出這麼多東西,遞歸的非遞歸的,全排列搜索的,回溯法的,甚至還有廣搜版本的八皇后…