回溯/遞歸——八皇后

問題描述:在8X8的棋盤上放置八個皇后,任意兩個皇后都不能處於同一行、列或者同一斜線上。
分析:每行只能放一個,每列只能放一個。

回溯

如何判段在當前位置是否可以防止皇后

行和列的判斷很簡單,主要是對角線的判斷
在 / 對角線上的點:行 + 列相等
在 \ 對角線上的點:行 - 列相等
通過建立皇后對象的結構體,重載==實現兩個皇后相等的判斷

	bool operator==(const Queen& obj){
		return (x == obj.x || y == obj.y ||
		(x + y) == (obj.x + obj.y) ||
		(x - y) == (obj.x - obj.y));
		return x == obj.x;
	}

建立Queen表示皇后

包含:座標,皇后相等的判斷

struct Queen
{
	int x, y;
	Queen(int xx = 0, int yy = 0) :x(xx), y(yy){}
	bool operator==(const Queen& obj){
		return (x == obj.x || y == obj.y ||
		(x + y) == (obj.x + obj.y) ||
		(x - y) == (obj.x - obj.y));
		return x == obj.x;
	}
	bool operator!=(const Queen&obj){
		return *this == obj;
	}
	void operator=(const Queen&obj){
		x = obj.x;
		y = obj.y;
	}

};

思路

顯而易見,每一行只能放一個皇后,因此我們選擇一行一行的防止皇后。我們將可以的滿足條件的Queen放入vector中。第一個皇后從(0,0)位置開始放置,並將其信息記錄到vector中,然後進入下一行,從(1,0)開始通過在vector中find當前Queen(重載過==)來判斷是否是合法位置,可以放置則入棧,不能則進入(1,1),循環進行。。。。

find函數

bool find(vector<Queen>v, Queen obj){
	int i = 0;
	for (i = v.size()-1; i >=0; i--){
		if (v[i] == obj)
			return true;;
	}
	return false;
}

直到這一行都沒有位置可以放置的時候,說明上次放置的位置不合理,出vector中剔除最後一個元素(等價於出棧),回到剔除元素的位置,換下一個位置判斷。。。

Queen pop(vector<Queen>&v){
	Queen q;
	q = v[v.size()-1];
	v.pop_back();
	return q;
}

當成功放置八個皇后後,同樣出棧回到上次的位置,換下一個位置判斷,這樣就可以得到所有的可能。

void placeQueens(int N){
	vector<Queen>v;
	Queen q(0, 0);
	do
	{
		if (N <= v.size() || N <= q.y){//非法點 出棧
			//若size==N說明已經找出一組解了 把當前棧頂同樣出棧  找下一組解
			q = pop(v);
			q.y++;
		}
		else
		{
			while ((q.y < N) && (find(v, q)))
退出while的可能:
	//1.q.y>=N跳出邊界,(跳出邊界前沒有找到不相同的 ) 
	//出現這中情況則說明在y<N的範圍內沒有合法點 所以上一次的點是不合法的 會出棧


   //2.q.y<N在邊界中合法,則只有後面的條件爲false 即和前面的點都不同 
   // 這種點就是當前的合法點,入棧。
			{
				q.y++;
			}
			if (N>q.y){
				v.push_back(q);//記錄當前合法點入棧信息x y
				q.x++;//行向下移動找下一行的合法點
				q.y = 0;
			}
		}
		if (v.size() >= N){
			print(v, N);
			//只有合法點纔會入棧 一共有N個合法點 所以當 
			//size==N時說明棧中的點就是一組解 total++
			total++;
			cout << total << endl;
		}
	} 
	while (0<q.x || q.y<N);
	//更容易理解的循環條件是while (!(q.x == 0 && q.y == N));
}

遞歸

相對而言遞歸就簡單的多,遞歸本質上相當於操作系統幫我們完成了入棧出棧的操作,因此我們只需要判斷遞歸的開始條件和終止條件即可。

思路:

我們用一個int ret[9]的數組來記錄皇后的位置,用數組下標表示皇后所在行,下標中對應元素表示列。

用函數void placeQueens2(int queennum)來實現目標,參數爲已經放置的皇后個數,每成功找到一個皇后則在 ret中記錄他的位置,然後
void placeQueens2(queennum+1)找下一個皇后

當下一個皇后找不到時 返回,並從ret中將ret[–queennum]的皇后信息清除(因爲不可用),return遞歸終止條件1

當queennum = 7時說明8個皇后都找到了,遞歸終止條件2

int ret[8] = { -1 };
int q_total = 0;
bool avaiable(int num, int pos){
	for (int i = 0; i < num; ++i){
		if (ret[i] == pos || (i + ret[i] == num + pos) ||
		 (i - ret[i] == num - pos))
			return false;
	}
	return true;
}
void print(int ret[]){
	for (int i = 0; i < 8;i++){
		cout << ret[i] << " ";
	}
	cout << endl;
}
void placeQueens2(int queennum){
	for (int i = 0; i < 8; i++){
		if (avaiable(queennum, i)){
			ret[queennum] = i;
			if (queennum<7)
				placeQueens2(queennum + 1);
			else{
				q_total++;
				print(ret);
				break;//這裏用break return 甚至什麼都不用結果都一樣
				//但是過程是不同的用break效率最高
			}
		}
	}
	ret[--queennum] = -1;//用--queennum也可以,因爲只要對皇后個數--後,
	//其內容就已經失效了
	return;
}

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