N皇后算法—優化版

N皇后問題

【題目描述】 N皇后問題

         世界歷史上曾經出現一個偉大的羅馬共和時期,處於權力平衡的目的,當時的政治理論家波利比奧斯指出:“事涉每個人的權利,絕不應該讓任何權利大的壓過其它力量,使他人無法立足於平等條件與之抗辯的地步。”這類似著名的N皇后問題,即在NXN格的國際象棋上擺放N個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,請問有多少中擺法,並將每種擺法打印出來。圖1所示即是擺法的一種。

【輸入格式】

輸入一個整數,即N(14>N>3)。

【輸出格式】

輸出所有擺法,每個擺法佔一行

【輸入樣例】

4

【輸出格式】

2413

3142

【輸出說明】

N=4的棋盤輸出的兩種方案即圖2所示


遞歸算法1

    若想遍歷所有擺法而無一遺漏,可以逐行從上至下、從左至右嘗試棋子的擺放。以N=4的棋盤爲例,其遍歷過程如圖3所示。


Tips:棋盤座標上一般會想到用二維數組來表示,如定義二維數組a[8][8],則a[0][0]=1代表棋盤第一行第一列有棋子,a[3][4]=0代表棋盤第4行第5列無棋子……但實際上只用一個一維數組可以解決該問題。

           

            例如,使用a[8]來表示棋盤座標時,假設a[0]=7,即表示第1行第7列有棋子,a[1]=2即表示第2行第2列有棋子,而且這種方法無須再判斷兩皇后是否在同一行。

         可以定義一個Try(x,y)函數判斷棋盤(x,y)處是否可以放皇后:

(1)不在同一列

(2)不在對角線上,即有兩棋子座標分別爲(X1,Y1),(X2,Y2),則|X1-X2|!=|Y1-Y2|

源碼:

//N皇后問題——遞歸算法
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
int n,a[21];


void print()				//打印棋子 
{int i=1;
	for(;i<=n;i++)  
		cout<<a[i];
	cout<<" ";
		

		
	if(i%7==0)
		cout<<endl;
} 


void printGraph()			//圖形化打印佈局
{
	system("color F0");  	//DOs命令改變背景色和前景色,顏色爲16進制數0~F
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=n;++j)
			if(a[i] == j)
				cout<<"O";
			else
				cout<<"▅";
		cout<<endl;
	} 
	cout<<endl;
} 


int Try(int x,int y)			//測試x行y列可否擺放棋子,成功返回1,否則返回0 
{
	int j=1;
	while(j<x)					//與數組中已放好的數比較 
	{
		if((a[j] == y) || (abs(x-j) == abs(a[j]-y)))
			return 0;
		++j;					//右移一格繼續嘗試 
	}
	return 1;
}

void place(int x)				//遞歸函數 
{
	int y;
	if(x>n)						//棋子第n行已擺好,則打印成功方案 
	{
		print();
		//printGraph();			//打印圖形化佈局 
	}
	else
	{
		for(y=1;y<=n;++y)		//該行棋子依次從左向右移 
			if(Try(x,y))		//如果可以擺放 
			{
				a[x]=y;			//給a[x]賦值 
				place(x+1);		//繼續下一行的遞歸 
			}
	}
}


int main()
{
	cin>>n;
	place(1);			//從第1行開始遞歸 
	return 0;
}

該程序中有個用於調試的printGraph()函數,選用它可圖形化顯示佈局方案,如圖所示:


爲了深入理解遞歸調用的過程,設N=4,則可將place(4)到place(4)的過程展開,如圖所示:


運行過程如圖下圖所示。

         模擬該程序運行過程時會發現,通過程序不斷的進進退退,最後就打印除了全部的佈局。

由此可知,遞歸中常常隱含回溯,回溯即選擇一條路走下去,發現走不通,就返回去再選一條路走。


以下爲八皇后問題的解決,共92種擺法:


改程序當N>13時,速度將慢的難以忍受,並且考慮到輸出方案浪費了大量的時間,此後的優化算法只需要輸出方案數即可。



遞歸算法2

         以N=4爲例,下圖左是正斜線,右是反斜線,即有2xN-1條、反斜線。


1號正斜線所佔據的棋盤單元爲(1,1)

2號正斜線所佔據的棋盤單元爲(1,2)(2,1)

3號正斜線所佔據的棋盤單元爲(1,3)(2,2)(3,1)

4號正斜線所佔據的棋盤單元爲(1,4)(2,3)(3,2)(4,1)

5號正斜線所佔據的棋盤單元爲(2,4)(3,3)(4,2)

6號正斜線所佔據的棋盤單元爲(3,4)(4,2)

7號正斜線所佔據的棋盤單元爲(4,4)

可以發現規律如下:

(1)   同一正斜線所佔據的棋盤單元行列之和相等,如上面7條正斜線分別爲2、3、4、5、6、7、8.

(2)   同一反斜線所佔據的棋盤單元行列之差相等。

故可以定義bool數組x1[ ]用來記錄行列之和爲i的正斜線是否已經被佔據,bool數組x2[ ]用來記錄行列之差爲i的反斜線是否已經被佔據。考慮到行列之差可能爲負數,棋盤座標(x,y)對應x2[ x-y+n]。

再定義一個bool數組y[ ]判斷列衝突,如果y[i]=1,說明前面已有皇后放到在這一列,再放會發生列衝突,y[i]=0,說明當前第i列還沒有放置皇后。


參考代碼:

//N皇后——遞歸算法2
#include<iostream>
#include<cstdio>
#include<cstdlib>

using namespace std;

int n,num;

int a[20]={0};
bool y[41],x1[41],x2[41];


void place(int x)   					//遞歸函數
{
	if(x>n)
		++num;							//找到一個擺法,計數器加1 
	else
	{
		for(int i=1;i<=n;++i)
		{
			if(y[i] == 0 && x1[x+i] ==0 && x2[x-i+n] == 0)		//如果列,正斜線,反斜線無衝突 
			{
				a[x] = i;						//給a[x]賦值 
				y[i] = 1;						//列座標做標記 
				x1[x+i] = 1;					//正斜線做標記 
				x2[x-i+n] = 1;					//反斜線做標記 
				place(x+1);						//繼續下一行的遞歸 
				y[i] = 0;						//恢復 
				x1[x+i] = 0;					//恢復正斜線標記爲0 
				x2[x-i+n] = 0;					//恢復反斜線標記爲0 
			}
		}
	}
} 




int main()
{
	cin>>n;
	place(1);
	cout<<num<<endl;
}

優化後的程序提高了很多,但是當N>=14時候,速度還是較慢,我們還可以再次基礎上繼續優化,這裏我就不繼續寫了,希望大家能夠自己優化。


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