1. 程式人生 > >【演算法複習二】八皇后問題 ---- 回溯

【演算法複習二】八皇后問題 ---- 回溯

一,問題描述

       在8X8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法。

二,分析

       採用逐步試探的方式,先從一個方向往前走,能進則進,不能進則退並嘗試另外的路徑。首先我們來分析一下國際象棋的規則,這些規則能夠限制我們的前進,也就是我們前進途中的障礙物。一個皇后q(x,y)能被滿足以下條件的皇后q(row,col)吃掉

       1)x=row(縱向不能有兩個皇后)

       2) y=col(橫向不能有兩個皇后

       3)col + row = y+x;(斜向正方向)

       4)col - row = y-x;(斜向反方向)

       遇到上述問題之一的時候,說明我們已經遇到了障礙,不能繼續向前了。我們需要退回來,嘗試其他路徑。

       我們將棋盤看作是一個8*8的陣列,這樣可以使用一種蠻幹的思路去解決這個問題,這樣我們就是在8*8=64個格子中取出8個的組合,C(64,8) = 4426165368,顯然這個數非常大,在蠻幹的基礎上我們可以增加回溯,從第0列開始,我們逐列進行,從第0行到第7行找到一個不受任何已經現有皇后攻擊的位置。

          

        前面四列的擺放如上圖則 第五列,我們會發現找不到皇后的安全位置

        第五列的時候,擺放任何行都會受到上圖所示已經存在的皇后的攻擊,這時候我們認為我們撞了南牆了,是回頭的時候了,我們後退一列,將原來擺放在第四列的皇后

(3,4)拿走,從(3,4)這個位置開始,我們在第四列中尋找下一個安全位置為(7,4),再繼續到第五列,發現第五列仍然沒有安全位置,回溯到第四列,此時第四列也是一個死衚衕了,我們再回溯到第三列,這樣前進幾步,回退一步,最終直到在第8列上找到一個安全位置(成功)或者第一列已經是死衚衕,但是第8列仍然沒有找到安全位置為止

       總結一下,用回溯的方法解決8皇后問題的步驟為:

            1>從第一列開始,為皇后找到安全位置,然後跳到下一列

            2>如果在第n列出現死衚衕,如果該列為第一列,棋局失敗,否則後退到上一列,在進行回溯

            3>如果在第8列上找到了安全位置,則棋局成功。

三,原始碼(精選自網友解答)

       回溯法非遞迴

#include<iostream>
using namespace std;

#define N 8
//N代表皇后數
void queen()
{
	int Count=0;		 //計算總共的解的數量
	int column[N+1];     //column[m]=n 表示第m列,第n行放置了皇后,這裡下表並從0開始
	int row[N+1];		 //row[m]=1表示第m行沒有皇后,=0表示有皇后
	int b[2*N+1];		 //b[m]=1表示第m條主對角線沒有皇后,
	int c[2*N+1];        //c[m]=1表示第m條次對角線沒有皇后,=0表示有皇后
	int numQueen=1;		 //計數已經放置的皇后數目,當numQueen=N時候則表示已經完成探測
	int good=1;			 //good=1表示沒有發生衝突,good=0表示發生衝突
	
	//初始化這些標記
	for(int j=0;j<N+1;++j)
	{
		row[j]=1; //沒有皇后 
	}
	for(int j=0;j<2*N+1;++j)
	{
		b[j]=c[j]=1;
	}
	column[1]=1;
	column[0]=0;          //初始化第一行第一列,第二行第二列放置皇后
	do
	{
		//沒有發生衝突,則繼續向下探測,增加皇后或者判斷當前是否是解
		if(good)
		{
			//當前皇后數是解,列印,繼續向下探測
			if(numQueen==N)
			{
				Count++;
				cout<<"找到解"<<endl;
				for(int j=1;j<N+1;++j)
				{
					cout<<j<<"列"<<column[j]<<"行"<<endl;
				}
				//最後一個棋子向下移動,移動到本列最後一個
				while(column[numQueen]==N)
				{
					numQueen--;			//皇后數減1,即列數減1,回溯
					//回溯後將該列以及該列最後一行狀態位修改
					//第numQueen列column[numQueen]行處狀態位置修改
					row[column[numQueen]]=1;
					b[numQueen+column[numQueen]]=1;
					c[N+numQueen-column[numQueen]]=1;
				}
				column[numQueen]++;		//回溯至上一行,向上一行的下一列繼續探測
			}
			//當前不是解,那麼繼續向下探測
			else
			{
				//改變該位置對應標誌
				row[column[numQueen]]=0;
				b[numQueen+column[numQueen]]=0;
				c[N+numQueen-column[numQueen]]=0;
				//本次位置沒有發生衝突,也不是正確解,那麼就應該向下探測下一列的第一行
				column[++numQueen]=1;
			}
		}
		//如果當前發生了衝突,就在本列繼續向下,如果到了本列最後一行,則回溯到上一列
		else
		{
			while(column[numQueen]==N) //到了本列最後一行,還是衝突,那麼回溯到上一列
			{
				numQueen--;
				row[column[numQueen]]=1;
				b[numQueen+column[numQueen]]=1;
				c[N+numQueen-column[numQueen]]=1;
			}
			column[numQueen]++;	//發生衝突了,又沒有到本列的最後一行,那麼在本列繼續向下一行探測
		}
		//檢測放置了這個位置後是否衝突
		good=row[column[numQueen]]&b[numQueen+column[numQueen]]&c[N+numQueen-column[numQueen]];
	}while(numQueen);
	cout<<N<<"皇后總共找到解:"<<Count<<"個"<<endl;
}
int  main()
{
	queen();
	system("pause");
	
	return 0;
}

 更牛解答