八皇后及回溯算法

   記着剛接觸到八皇后的問題時,自己總是想不通如何使判斷步驟退回來,自己套了好多個循環,最後還是這種情況
 1  0  0  0  0  0  0  0
 0  0  1  0  0  0  0  0
 0  0  0  0  1  0  0  0
 0  0  0  0  0  0  1  0
 0  1  0  0  0  0  0  0
 0  0  0  1  0  0  0  0
 0  0  0  0  0  1  0  0
 0  0  0  0  0  0  0  0
後來想一想,誒呀,我總順着往下,在沒位置可填的情況下,無法進行新的路線,
也就是我寫了好多好多,循環,卻只是這一種情況,那是因爲什麼呢?
就是因爲沒有往回走。
上面那張情況就是前7行都很符合規定的填入了1,1代表有皇后。
然後到第8行的時候,每個位置都判斷失敗,也就是說倒數第二行的倒數第三個數就是個死路入口(紅色1)!

然後我就想幹脆用遞歸,如果本次(fx(arr,i))有位置可填入的話就調用自己fx(arr,i+1),讓下一行進行判斷填入操作。判斷如果本行沒有位置可填入1的話,就代表上一行某個位置是死路,我就清除上一行,調用自己,參數設置爲i-1.  fx(arr,i-1)然後從上一行重新選位置。
但是該選哪個位置呢?從開頭0下標選的話還是會選到那個死路入口啊。

然後我又想幹脆函數設置的參數再多一個,傳進去上一行填入1的那個位置的下標的參數 總可以了吧。
假如第8行失敗了,沒有找到符合的位置然後調用fx(arr,i-1,k+1) 讓每次遍歷某行時從k+1下標開始總可以了吧。
然而還是失敗了,先不說裏面有很多邏輯錯誤,例如連着2次失敗的話,無法找到2行前的下標值,什麼意思呢?
就是如果8行失敗了,調用個fx(arr,i-1,k+1);  到第7行,從k+1  (5 )開始,如果也全失敗了無位置可填怎麼辦,繼續返回fx(arr,i-1,k+1)嗎?顯然不行的,因爲k+1是第7行傳入的(k+1)+1兩次調用後得來的,值爲7   但此時第6行明明是下標爲3的地方填的是1,這明顯不正確?

然後我又加入了好多判斷條件呀。如果是因爲失敗返回的帶個flag=false的參數,又要獲取它上一行哪個地方是1的下標,然後從下標處開始遍歷呀。。。。

其實到最後先不說是否邏輯上有問題,我運行後直接爆棧了!!複雜的判斷導致多次遞歸,無法釋放,最後結果沒出來,已經癱瘓了。

現在想想還是覺得太傻了,因爲當時根本沒有理解回溯的思想,對於遞歸的掌握也很模糊,所以導致無法及時給出遞歸的出口,1m的空間根本不夠我胡折騰!

後來也是看了些有關回溯的資料才突然恍然大悟!!
話轉回來,不管怎麼樣,總之現在搞清楚了,也比一直蒙圈的要強。
以下講講回溯法和八皇后的解法吧
其實比起簡單的遞歸來說,就是多了個深度(出口的另一種說法),還有走到死路後回溯的到死路前一個路口的做法。一般是循環遍歷到下一個數。 然後做善後工作,意思就是遇到死路後的善後做法。比如8皇后,如果一條路死了,自然要把死路前那行的皇后給清0了,否則循環遍歷到下一個數怎麼也無法判斷可放位置了。


我們先引入一個判斷函數;它的參數爲arr整數數組,行列下標
bool Queen(int *arr, int h,int l);
它返回的是bool值,如果傳入的那個arr[h][l]在當前arr數組中合法符合放皇后的規則,就返回true值,
如果不合法就返回false


以下爲簡單的示意代碼。
例如定義遞歸的函數名就是尋找皇后!
遞歸的函數:
i爲層數的下標,即是8*8數組的行數的下標
void 尋找皇后(int i)
{
  if(n>7)  //因爲8*8數組的行下標是0-7.如果大於7了。就代表這個i是從上個遞歸的7傳下來的。就意味着
                     //前8行全部合法,且都有放入皇后,那麼我們就該打印結果了。可以把這個n>7稱之爲探索深度,                      //或者是出口
   {
    show();   //進入if就代表佈局成立,打印出來

   }

   else
    {       
              for(int  j=0;j<8;j++)
            {
               if(Queen(arr,i,j))
                 {
                   尋找皇后(arr,i+1);
                 }
                arr[i][j]=0;                      //這就是所謂的遇到死路後的善後工作,如果上面有進入 尋找皇后 函數
                                                     // 不管它下面展開的遞歸是失敗了(遇到死路)或成功了(找到出口),
                                                   //那些遞歸下去的函數始終會執行完它們自己的代碼,然後結束,收束上來
                                                 //或者稱回溯上來。這條路已經不能走了(是死路或者有出口,有出口意味着
                                                //第8排能放值,8皇后佈局成立,已經打印到屏幕了,但我們要的是所有8皇后
                                              //成立的情況,所以仍然要回溯上來進行下一種佈局的判斷)
                                            //回溯上來後,要捨棄剛走過的路,所以把arr[i][j]置爲0,然後函數繼續運行,會進                                            //入下一次循環,使j++;這就意味着剛捨棄的路的右邊一條路開始尋求路線了,符                                          //合我們的預期!!
             }
    }


}


有人會問怎麼沒有出現過 尋找皇后(i-1)   的字眼呢,不-1怎麼跳到上一行進行回溯啊?
其實當初我也對此困惑了,但後來把遞歸簡單的畫一畫就一目瞭然了!
遞歸出的新函數,如果因爲任何原因死路了,它就會運行結束,就是棧的原理,它一結束,就該輪到遞歸出它的父函數運行了(它的父函數之前卡在for循環裏某一環就 遞歸了,在遞歸出的新函數完成前,父函數本身是暫掛在棧中的)。它的父函數的i早已經確定了,雖然父函數的i和父函數遞歸出來的函數(後面且稱之爲子函數吧)
i的值沒有什麼賦值關係。但其實父函數的i相比子函數的i就已經少過1了(因爲我們每次遞歸都是尋找皇后i+1的傳參)。不需要我們再自作聰明傳個什麼i-1之類的,完全弄巧成拙之舉。

最後貼上自己的比較拙劣的代碼,雖然比起各種修剪過的代碼來說效率不高,多餘操作,多餘判斷沒有省略,代碼也不精簡,但我認爲還是很容易理解的。
八皇后

八皇后
結果:一共92中排法
八皇后
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章