標籤:
問題描述:
要在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.....具體代碼如下:
[cpp] view plaincopyprint?
- 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");
- } } } } } } } } }
[cpp] view plaincopyprint?
- /////////////////////////////////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迴圈嗎?我們來把代碼最佳化一下,用到的思想還是和方法一相同:深度優先搜尋、回溯。具體代碼如下:
[cpp] view plaincopyprint?
- 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、遞迴實現。上面兩種方法都是用到了深度優先搜尋,而一般而言,深度優先搜尋都是可以用遞迴來實現的。下面我們用遞迴的方式解決八皇后問題。具體代碼如下:
[cpp] view plaincopyprint?
- int a[20],n,i,j,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",j,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
八皇后之回溯法解決[轉]