算法分析 | 回溯法 | N皇后問題

一.問題分析

N×N的棋盤上有N個皇后,皇后的攻擊範圍是同一行,同一列和對角線.

N個皇后要如何安全排列?

在一個二維數組中,一行一行地看,觀察第 i 行的第 i 個皇后能放在第幾列.也就是說,默認每個皇后不可能出現在同一行.

↑顯約束.

所以,在判斷第t個皇后時,只需判斷是否

a.列衝突:x[t]==x[i]                          i的範圍是1~t-1 

b.對角線衝突:t-i==abs(x[t]-x[i])      i的範圍是1~t-1 

↑隱約束

 

二.代碼實現

分爲三個部分

1.全局變量區

2.約束函數

3.遞歸函數

4.調用遞歸函數

 

1.全局變量

#include<cmath>

const int M = 20;
int n;//表示n個皇后
int x[M];//例如:x[2]=5表示第2行的皇后在第5列,數組稍寬些會少許多麻煩
int countn=0;//n皇后解的個數

 

2.約束函數

//形參t表示第t個皇后能否與前0~t-1個皇后不衝突

bool Place(int t)//第t個皇后能否放在第i個位置
{
	bool flag = true;
	for (int i = 1; i < t; i++)//觀察是否與第1~t-1個皇后衝突
	{
		//在同一列 或 對角線 上
		if (x[t] == x[i] || t - i == fabs(x[t] - x[i]))
		{
			flag = false;
			break;
		}
	}

	return flag;
}

 

3.遞歸函數

一個形式上很簡潔的遞歸.

尚未遍歷到第n行時,遍歷每一列,查找允許的安全位置

能夠遍歷到第n行,說明得出一個解,打印出來

"數組是從0開始的" 這一點有時候很棘手,不如在設置數組時多設置一單位長度.統一從1開始

void queen(int t)		//當前遍歷的第t個皇后
{
	if (t > n2)
	{
		countn++;//表示找到一個解
		for (int i = 1; i <= n2; i++)
		{
			cout << x[i] << "\t";
		}
		cout << endl;
		cout << "------------" << endl;
	}
	else
	{
		for (int i = 0; i < n2; i++)//分別判斷n個分支
		{
                        l[t] = i;//※(第一次的bug)若不添加該語句,x[]始終處於剛剛初始化的狀態即memset(x,0,sixeof(x)),程序無法運行出結果
			if (Place(t))
			{
				queen(t + 1);
			}
		}
	}
}

4.main()

cout << "請輸入1~20以內的數作爲皇后個數:";
cin >> n2;//Queen.h定義的變量,也可以像函數那樣被main()調用
if (n2 > 20) 
{
    cout << "輸入值偏大,請重新輸入:";
    cin >> n2;
    }
queen(1);
cout << "解的個數:	" << countn;

 

三.拓展優化

回溯法的優化,就是要減小解空間的大小,精簡m叉樹.具體來說,就是將隱約束 變成 顯約束.

在以上代碼中,0~n-1的每一個分支都要判斷到並遞歸,這顯然是極大的浪費.

通過隱約束可知,不可能是同一列,可以設置一個bool live[n]數組用於判斷之前元素佔的是哪一列.

在for()裏嵌一個if()判斷來減少循環次數.

最後效果如圖:

 

 

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