1. 程式人生 > >八皇后問題各種解法分析

八皇后問題各種解法分析

遞迴與回溯:

   a.回溯演算法的基本思想:從問題的某一種狀態出發,搜尋可以到達的所有狀態。當某個狀態到達後,可向前回退,並繼續搜尋其他可達狀態。當所有狀態都到達後,回溯演算法結束!    b.對於回溯演算法,在前面KMP匹配中就利用了這個思想,只不過當時KMP中定義了一個node陣列(起到了一個地圖的作用,記錄了每種回溯情況的可能)。而這節中,是利用函式的活動物件儲存回溯演算法的狀態資料,因此可以利用遞迴完成回溯演算法!

2.八皇后問題:

   a.問題背景:國際象棋是一個8*8的矩陣,在棋盤中同時放下8個皇后,且互相不攻擊的情況叫八皇后問題    b.如圖,說明八皇后問題中的回溯演算法:       

注意:其實就是不斷的通過遞迴函式,去往棋盤中嘗試放皇后,成功就繼續遞迴(即繼續放皇后),失敗就跳出遞迴函式,回溯到上層遞迴函式中,上層遞迴函式中儲存著上一個皇后的位置!!!這就是八皇后中,回溯的概念!     c.八皇后的演算法思路:        第一、為了更加方便我們表示棋盤,我們使用一個10*10的二維陣列來表示8*8的棋盤加棋盤邊框。
  1. char board[10][10];  
       第二、初始化棋盤,把棋盤邊框標記為'#',把棋盤中的位置都標記為‘ 空格 ’
  1. void init()  
  2. {  
  3.     int i = 0;  
  4.     int j = 0;  
  5.     for
    (i=0; i<N+2; i++)  
  6.     {  
  7.         board[0][i] = '#';  
  8.         board[N+1][i] = '#';  
  9.         board[i][0] = '#';  
  10.         board[i][N+1] = '#';  
  11.     }  
  12.     for(i=1; i<=N; i++)  
  13.     {  
  14.         for(j=1; j<=N; j++)  
  15.         {  
  16.             board[i][j] = ' ';  
  17.         }  
  18.     }  
  19. }  
       第三、定義一個棋盤的列印函式display(),方便測試
  1. void
     display()  
  2. {  
  3.     int i = 0;  
  4.     int j = 1;  
  5.     for(i=0; i<N+2; i++)  
  6.     {  
  7.         for(j=0; j<N+2; j++)  
  8.         {  
  9.             printf("%c", board[i][j]);  
  10.         }  
  11.         printf("\n");  
  12.     }  
  13. }  
      第四、我們定義一個檢查的函式check(),來判斷這個位置是否可以放皇后,對於檢查,我們檢查三個方向,左上(-1,-1) 、 右上(-1,1)  、正上(-1,0)   對於橫排我們是不檢測的,因為我們每一排就放一個皇后,皇后用' * '來表示!
  1. typedefstruct _tag_pos //定義一個數據結構來充當方向 
  2. {  
  3.     int i;  
  4.     int j;  
  5. }Pos;  
  6. /*檢測三個方向  左上  右上  正上   橫排是不檢測的 因為一排只放一個*/
  7. static Pos pos[3] = {{-1,-1},{-1,1},{-1,0}};  
  8. int check(int i, int j)  
  9. {  
  10.     int p = 0;   
  11.     int ret = 1;  
  12.     for(p = 0; p < 3; p++) //檢測三個方向 
  13.     {  
  14.         int ni = i;  
  15.         int nj = j;  
  16.         while(ret && (board[ni][nj] != '#'))//判斷沒有到達棋盤邊界 
  17.         {  
  18.             ni = ni + pos[p].i;  
  19.             nj = nj + pos[p].j;  
  20.             ret = ret && (board[ni][nj] != '*');//判斷這個方向沒有放過皇后 
  21.         }  
  22.     }   
  23.     return ret; //可以放皇后返回1  不可返回0 
  24. }  
      第五、是八皇后演算法的核心,就是放皇后的函式find(),這個函式是這樣的!先從第一行開始,進行對列數j的for迴圈,在for迴圈中判斷(i,j)是否可以放皇后,一旦可以放皇后,就放置一個皇后,然後find(i+1),進入第二行(即第二層遞迴)。繼續做同樣的判斷,這個遞迴函式有兩個出口,同理,就有兩種情況結束遞迴,一種是,i超過了第八行,也就是說,皇后全部放置完全,所以應該display()列印!另一種是,for迴圈結束,依然沒有check()成功,說明前面有皇后放錯了!導致這一行不能放皇后!應該進行回溯!!!所以結束這個錯誤的遞迴,返回到上一層遞迴,清除上層遞迴中放錯的皇后   board[i][j] = ' ';  繼續進行上一行的for迴圈!!!這裡需要補充的是,即使i超過了第八行,皇后放置完全了,函式依然也會返回到上一行,去進行上一行的for迴圈,擦除皇后位置,嘗試尋找新八皇后的情況!!!
  1. void find(int i)  
  2. {  
  3.     int j = 0;  
  4.     if(i > N) //判斷是否已經超過了第八行 
  5.     {  
  6.         coust++; //計算八皇后情況的個數 
  7.         display();   
  8.         //getchar();
  9.     }  
  10.     else
  11.     {  
  12.         for(j = 1; j <= N; j++) //判斷一行 是否有匹配的位置 
  13.         {  
  14.             if(check(i,j))  
  15.             {  
  16.                 board[i][j] = '*'//放置皇后 
  17.                 find(i+1);  
  18.                 board[i][j] = ' '//清除放錯的皇后 
  19.             }  
  20.         }  
  21.     }  
  22. }   
注意:之所以說,八皇后中利用遞迴來進行回溯,就是因為當遞迴結束的時候,會返回到上一層放置皇后的位置(即棧空間中,儲存了上行皇后i,j的情況)
看著上面的動態想象下,當有時候,出現過的皇后又消失了,就是因為for迴圈結束了,還沒有check成功,導致遞迴結束,返回到上層遞迴,並且擦除上層皇后的位置,並且繼續for迴圈,如果這層for中check依然不成功,繼續回溯,這個消失的過程就是回溯過程!與動圖不同的是,我們的程式,不會在找完一種情況後就結束!即使成功匹配,程式也會向上層回溯,去尋找其它的情況,直到第一行for迴圈結束(即 i=1 的時候),程式才會停止!

本節程式碼:

八皇后問題的完整程式碼:
  1. #include <stdio.h>
  2. #define N 8
  3. staticchar board[N+2][N+2];  
  4. staticint coust = 0; //記錄八皇后個數 
  5. typedefstruct _tag_pos //定義一個數據結構來充當方向 
  6. {  
  7.     int i;  
  8.     int j;  
  9. }Pos;  
  10. /*檢測三個方向  左上  右上  正上   橫排是不檢測的 因為一排只放一個*/
  11. static Pos pos[3] = {{-1,-1},{-1,1},{-1,0}};  
  12. void init()  
  13. {  
  14.     int i = 0;  
  15.     int j = 0;  
  16.     for(i=0; i<N+2; i++)  
  17.     {  
  18.         board[0][i] = '#';  
  19.         board[N+1][i] = '#';  
  20.         board[i][0] = '#';  
  21.         board[i][N+1] = '#';  
  22.     }  
  23.     for(i=1; i<=N; i++)  
  24.     {  
  25.         for(j=1; j<=N; j++)  
  26.         {  
  27.             board[i][j] = ' ';  
  28.         }  
  29.     }  
  30. }  
  31. void display()  
  32. {  
  33.     int i = 0;  
  34.     int j = 1;  
  35.     for(i=0; i<N+2; i++)  
  36.     {  
  37.         for(j=0; j<N+2; j++)  
  38.         {  
  39.             printf("%c ", board[i][j]);  
  40.         }  
  41.         printf("\n");  
  42.     }  
  43. }  
  44. int check(int i, int j)  
  45. {  
  46.     int p = 0;   
  47.     int ret = 1;  
  48.     for(p = 0; p < 3; p++) //檢測三個方向 
  49.     {  
  50.         int ni = i;  
  51.         int nj = j;  
  52.         while(ret && (board[ni][nj] != '#'))//判斷沒有到達棋盤邊界 
  53.         {  
  54.             ni = ni + pos[p].i;  
  55.             nj = nj + pos[p].j;  
  56.             ret = ret && (board[ni][nj] != '*');//判斷這個方向沒有放過皇后 
  57.         }  
  58.     }   
  59.     return ret; //可以放皇后返回1  不可返回0 
  60. }  
  61. void find(int i)  
  62. {  
  63.     int j = 0;  
  64.     if(i > N) //判斷是否已經超過了第八行