八皇后之回溯法解決

九茶發表於2014-11-23

問題描述:

要在8*8的國際象棋棋盤中放8個皇后,使任意兩個皇后都不能互相吃掉。規則是皇后能吃掉同一行、同一列、同一對角線的棋子。如下圖即是兩種方案:



解決方案:

8*8的棋盤要擺放8個皇后,且不能同行同列同對角線,那麼每行必定會有一個皇后。我們可以設一個陣列a用來存放每一行皇后的位置,元素值表示第幾列(如a[1]=5表示第一行的皇后處於第五個格)。然後只需要求出陣列a的值 問題就解決了,下面介紹三種回溯解法:


1、八個for迴圈。用列舉的辦法,八個for迴圈分別列舉每一行的8個位置,但是我們不用全部列舉完,可以採用“剪枝策略”,即遇到不適合的情況就回溯。例如當a[1]=4,第二行a[2]=4與a[1]同列,不符合題意。接下來的六個迴圈就不用窮舉下去了,直接"continue;"去檢驗a[2]=5.....具體程式碼如下:

void main()
{
	int a[9];
	int i,t=1;
	for(a[1]=1;a[1]<9;a[1]++)
		for(a[2]=1;a[2]<9;a[2]++)
		{
			if(!Check(a,2))	continue;
			for(a[3]=1;a[3]<9;a[3]++)
			{
				if(!Check(a,3))	continue;
				for(a[4]=1;a[4]<9;a[4]++)
				{
					if(!Check(a,4))	continue;
					for(a[5]=1;a[5]<9;a[5]++)
					{
						if(!Check(a,5))	continue;
						for(a[6]=1;a[6]<9;a[6]++)
						{
							if(!Check(a,6))	continue;
							for(a[7]=1;a[7]<9;a[7]++)
							{
								if(!Check(a,7))	continue;
								for(a[8]=1;a[8]<9;a[8]++)
								{
									if(!Check(a,8))	continue;
									else 
									{
										printf("第%d種解法:\n",t++);
										for(i=1;i<9;i++)
											printf("第%d個皇后:%d\n",i,a[i]);
										printf("\n\n");
}		}	}	}	}	}	}	}	}

/////////////////////////////////Check函式功能:檢驗第n行的皇后是否和之前的皇后有衝突,沒有的話返回1
int Check(int a[],int n)
{
	for(int i=1;i<n;i++)
	{
		if(abs(a[i]-a[n])==abs(i-n) || a[i]==a[n])//////////////見下面註釋
			return 0;
	}
	return 1;
}

程式碼註釋:

某一行的皇后a[n]不能和之前的皇后a[i]位置有衝突,約束條件為:

a、不在同一列:a[n] != a[i]

b、不在同一行:因為現在是每一行求一個皇后的位置,所以同一行不會有衝突,不需要考慮。

c、不在同一對左角線:a[n]-a[i] != n-i

d、不在同一右對角線:a[n]-a[i] != -(n-i)

條件c和d可以合成:abs(a[n]-a[i]) != abs(n-i)

總結:其實這裡用到的就是深度優先搜尋的思想,從第一行的皇后一直深入去找第二行、第三行...皇后的位置。其中加上了約束條件Check函式進行“剪枝”。這就是回溯演算法的思想:深度優先搜尋,遇到不滿足的情況就進行回溯。



2、方法一的優化。上述程式碼易讀、易懂,但是用八個for迴圈不免顯得很累贅,而且如果要求在100*100的棋盤上放100個皇后這種“N皇后問題“呢?難道用100個for迴圈嗎?我們來把程式碼優化一下,用到的思想還是和方法一相同:深度優先搜尋、回溯。具體程式碼如下:

void main()
{
	int a[256]={0};
	int i=1,j,n,t=1;////////////////////////////////////i表示當前正在搜尋第i行的皇后位置
	printf("請輸入幾皇后?n=");
	scanf("%d",&n);
	while(i>0)
	{
		for(a[i]++;a[i]<=n;a[i]++)
		{
			if(Check(a,i))//////////////////////////////如果第i行的皇后與之前的皇后位置上沒有衝突,則break跳出迴圈
				break;
		}
		if(a[i]<=n)/////////////////////////////////////如果a[i]<=n,即上面的for迴圈是由“break;”跳出來的,即第i行皇后的位置符合條件
		{
			if(i==n)////////////////////////////////////找到一組解,輸出
			{
				printf("第%d種解法:\n",t++);
				for(j=1;j<=n;j++)
					printf("第%d個皇后:%d\n",j,a[j]);
				printf("\n\n");
			}
			else////////////////////////////////////////未找完
			{
				i++;
				a[i]=0;
			}
		}
		else
			i--;////////////////////////////////////////回溯
	}
}
程式碼註釋:上面用到的Check函式和方法一的Check函式相同。

總結:雖然上面程式碼中只用到兩層迴圈,但是思想、思路和方法一都是一樣的,時間複雜度也是和方法一的時間複雜度相同。當n大於10之後運算就已經比較困難了。



3、遞迴實現。上面兩種方法都是用到了深度優先搜尋,而一般而言,深度優先搜尋都是可以用遞迴來實現的。下面我們用遞迴的方式解決八皇后問題。具體程式碼如下:

int a[20],n,i,t=1;////////////////////////////////////////全域性變數

void Try(int i)
{
	int j,k;
	for(j=1;j<=n;j++)
	{
		a[i]=j;
		if(Check(a,i))///////////////////////////////////////如果第j列不會與之前的皇后衝突
		{
			if(i<n)//////////////////////////////////////////如果i<n,即還沒有找到八個皇后,繼續遞迴
				Try(i+1);
			else ////////////////////////////////////////////如果找到了一組解就輸出
			{
				printf("第%d種解法:\n",t++);
				for(k=1;k<=n;k++)
					printf("第%d個皇后:%d\n",k,a[k]);
				printf("\n\n");
}	}	}	}

void main()
{
	printf("幾皇后?n=");
	scanf("%d",&n);
	Try(1);
}

 程式碼註釋:

a、此處遞迴的思路很簡單,每一層遞迴表示一行皇后,j表示列,即a[i]=j表示第i行的皇后位置在第j列。

b、以上用到的Check函式與方法一的Check函式相同。

我不清楚是什麼原因,遞迴的速度竟然明顯比前面的兩種方法快??


轉載請註明出處,謝謝!(原文連結:http://blog.csdn.net/bone_ace/article/details/41419695

相關文章