八皇后及回溯算法
記着剛接觸到八皇后的問題時,自己總是想不通如何使判斷步驟退回來,自己套了好多個循環,最後還是這種情況
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之類的,完全弄巧成拙之舉。
最後貼上自己的比較拙劣的代碼,雖然比起各種修剪過的代碼來說效率不高,多餘操作,多餘判斷沒有省略,代碼也不精簡,但我認爲還是很容易理解的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.