N皇后問題專題

算法報告之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值越大,求解速度提高的越多。
研究了兩天各類八皇后寫法,感覺自己對位運算的運用加深了一個層次,而且算法的設計如果能夠遵循計算機的運行方式,將會獲得更高的效率,更大的收穫還是知道了自己的渺小,一個原以爲十分簡單的八皇后都可以衍生出這麼多東西,遞歸的非遞歸的,全排列搜索的,回溯法的,甚至還有廣搜版本的八皇后…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章