八皇后問題各種解法分析

遞歸與回溯:

   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.       
  6.     for(i=0; i<N+2; i++)  
  7.     {  
  8.         board[0][i] = '#';  
  9.         board[N+1][i] = '#';  
  10.         board[i][0] = '#';  
  11.         board[i][N+1] = '#';  
  12.     }  
  13.       
  14.     for(i=1; i<=N; i++)  
  15.     {  
  16.         for(j=1; j<=N; j++)  
  17.         {  
  18.             board[i][j] = ' ';  
  19.         }  
  20.     }  
  21. }  
       第三、定義一個棋盤的打印函數display(),方便測試
  1. void display()  
  2. {  
  3.     int i = 0;  
  4.     int j = 1;  
  5.       
  6.     for(i=0; i<N+2; i++)  
  7.     {  
  8.         for(j=0; j<N+2; j++)  
  9.         {  
  10.             printf("%c", board[i][j]);  
  11.         }  
  12.           
  13.         printf("\n");  
  14.     }  
  15. }  
      第四、我們定義一個檢查的函數check(),來判斷這個位置是否可以放皇后,對於檢查,我們檢查三個方向,左上(-1,-1) 、 右上(-1,1)  、正上(-1,0)   對於橫排我們是不檢測的,因爲我們每一排就放一個皇后,皇后用' * '來表示!
  1. typedef struct _tag_pos //定義一個數據結構來充當方向   
  2. {  
  3.     int i;  
  4.     int j;  
  5. }Pos;  
  6. /*檢測三個方向  左上  右上  正上   橫排是不檢測的 因爲一排只放一個*/   
  7. static Pos pos[3] = {{-1,-1},{-1,1},{-1,0}};  
  8.   
  9. int check(int i, int j)  
  10. {  
  11.     int p = 0;   
  12.     int ret = 1;  
  13.     for(p = 0; p < 3; p++) //檢測三個方向   
  14.     {  
  15.         int ni = i;  
  16.         int nj = j;  
  17.           
  18.         while(ret && (board[ni][nj] != '#'))//判斷沒有到達棋盤邊界   
  19.         {  
  20.             ni = ni + pos[p].i;  
  21.             nj = nj + pos[p].j;  
  22.               
  23.             ret = ret && (board[ni][nj] != '*');//判斷這個方向沒有放過皇后   
  24.         }  
  25.     }   
  26.     return ret; //可以放皇后返回1  不可返回0   
  27. }  
      第五、是八皇后算法的核心,就是放皇后的函數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.   
  3. #define N 8  
  4.   
  5. static char board[N+2][N+2];  
  6. static int coust = 0; //記錄八皇后個數   
  7. typedef struct _tag_pos //定義一個數據結構來充當方向   
  8. {  
  9.     int i;  
  10.     int j;  
  11. }Pos;  
  12. /*檢測三個方向  左上  右上  正上   橫排是不檢測的 因爲一排只放一個*/   
  13. static Pos pos[3] = {{-1,-1},{-1,1},{-1,0}};  
  14.   
  15.    
  16. void init()  
  17. {  
  18.     int i = 0;  
  19.     int j = 0;  
  20.       
  21.     for(i=0; i<N+2; i++)  
  22.     {  
  23.         board[0][i] = '#';  
  24.         board[N+1][i] = '#';  
  25.         board[i][0] = '#';  
  26.         board[i][N+1] = '#';  
  27.     }  
  28.       
  29.     for(i=1; i<=N; i++)  
  30.     {  
  31.         for(j=1; j<=N; j++)  
  32.         {  
  33.             board[i][j] = ' ';  
  34.         }  
  35.     }  
  36. }  
  37.   
  38. void display()  
  39. {  
  40.     int i = 0;  
  41.     int j = 1;  
  42.       
  43.     for(i=0; i<N+2; i++)  
  44.     {  
  45.         for(j=0; j<N+2; j++)  
  46.         {  
  47.             printf("%c ", board[i][j]);  
  48.         }  
  49.           
  50.         printf("\n");  
  51.     }  
  52. }  
  53.   
  54. int check(int i, int j)  
  55. {  
  56.     int p = 0;   
  57.     int ret = 1;  
  58.     for(p = 0; p < 3; p++) //檢測三個方向   
  59.     {  
  60.         int ni = i;  
  61.         int nj = j;  
  62.           
  63.         while(ret && (board[ni][nj] != '#'))//判斷沒有到達棋盤邊界   
  64.         {  
  65.             ni = ni + pos[p].i;  
  66.             nj = nj + pos[p].j;  
  67.               
  68.             ret = ret && (board[ni][nj] != '*');//判斷這個方向沒有放過皇后   
  69.         }  
  70.     }   
  71.     return ret; //可以放皇后返回1  不可返回0   
  72. }  
  73.    
  74. void find(int i)  
  75. {  
  76.     int j = 0;  
  77.     if(i > N) //判斷是否已經超過了第八行   
  78.     {  
  79.         coust++; //計算八皇后情況的個數   
  80.         display();   
  81.         //getchar(); //起到斷點的作用   
  82.     }  
  83.     else  
  84.     {  
  85.         for(j = 1; j <= N; j++) //判斷一行 是否有匹配的位置   
  86.         {  
  87.             if(check(i,j))  
  88.             {  
  89.                 board[i][j] = '*'//放置皇后   
  90.                 find(i+1);  
  91.                 board[i][j] = ' '//清除放錯的皇后   
  92.             }  
  93.         }  
  94.     }  
  95. }   
  96. int main()  
  97. {  
  98.     init();  
  99.     //display();  
  100.     find(1);   
  101.     printf("coust is %d \n",coust);  
  102.     return 0;  
  103. }   
注意:八皇后問題,一共有92種情況!

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