遞迴思想與典型問題淺析
遞迴需要遵守的重要規則
1) 執行一個方法時,就建立一個新的受保護的獨立空間(棧空間)
2) 方法的區域性變數是獨立的,不會相互影響, 比如n變數
3) 如果方法中使用的是引用型別變數(比如陣列),就會共享該引用型別的資料.
4) 遞迴必須向退出遞迴的條件逼近,否則就是無限遞迴,出現StackOverflowError
5) 當一個方法執行完畢,或者遇到return,就會返回,遵守誰呼叫,就將結果返回給誰,同時當方法執行完畢或者返回時,該方法也就執行完畢。
迷宮問題
說明:
1) 小球得到的路徑,和程式設計師設定的找路策略有關即:找路的上下左右的順序相關
2) 再得到小球路徑時,可以先使用(下右上左),再改成(上右下左),看看路徑是不是有變化
3) 測試回溯現象
4) 思考: 如何求出最短路徑?
思路:計算每種走的策略(初始方向、順逆時針等)的陣列中2的數量,最少的即為最短路徑
其中一種策略的程式碼實現:
1. publicclassmigong{ 2. publicstaticvoidmain(String[]args){ 3. //定義一個二維陣列表示所走的迷宮地圖 4. int[][]map=newint[8][7];//預設初始值為0 5. //初始化迷宮:將迷宮外圍建造圍牆 6. for(int[]row:map){ 7. row[0]=1;//每一行的第一列置為1 8. row[6]=1;//每一行的第最後一列置為1 9. } 10. for(inti=0;i<map[0].length;i++){11. map[0][i]=1;//第一行的每一列置為1 12. map[map.length-1][i]=1;//最後一行的每一列置為1 13. } 14. //設定中間的擋板 15. map[3][1]=1; 16. map[3][2]=1; 17. map[2][2]=1; 18. 19. //遍歷初始迷宮 20. for(int[]row:map){ 21. for(intdata:row){ 22. System.out.print(data+""); 23. } 24. System.out.println(); 25. } 26. System.out.println("************************");27. 28. //通過遞迴來找路 29. setWay(map,1,1,1,5); 30. 31. //遍歷所走的地圖 32. for(int[]row:map){ 33. for(intdata:row){ 34. System.out.print(data+""); 35. } 36. System.out.println(); 37. } 38. } 39. 40. /** 41. *@parammap迷宮圖 42. *@parami入口的行座標 43. *@paramj入口的列座標 44. *@parama出口的行座標 45. *@paramb出口的列座標 46. *@return是否找到通路,true:找到false:沒找到 47. *說明: 48. *1.map表示地圖 49. *2. i , j表示從地圖的哪個位置開始出發(1,1) 50. *3. 如果小球能到map[a][b]位置,則說明通路找到. 51. *4. 約定:當map[i][j]為0:表示該點沒有走過, 52. * 當為1:表示牆,當為2:表示通路可以走,當為3:表示該點已經走過,但是走不通 53. *5.在走迷宮時,需要確定一個策略(方法):下->右->上->左,如果該點走不通,再回溯 54. */ 55. privatestaticbooleansetWay(int[][]map,inti,intj,inta,intb){ 56. if(map[a][b]==2){//說明走到了出口 57. returntrue; 58. }else{ 59. if(map[i][j]==0){//如果當前這個點還沒有走過 60. //按照策略下->右->上->左走 61. map[i][j]=2;//假定該點是可以走通. 62. 63. if(setWay(map,i+1,j,a,b)){//向下走 64. returntrue; 65. }elseif(setWay(map,i,j+1,a,b)){//向右走 66. returntrue; 67. }elseif(setWay(map,i-1,j,a,b)){//向上走 68. returntrue; 69. }elseif(setWay(map,i,j-1,a,b)){//向左走 70. returntrue; 71. }else{//說明為死路,將此格置為3,並回溯 72. map[i][j]=3;//將2改為3,說明此格為死路 73. returnfalse; 74. } 75. }else{//說明此時值為1/2/3 76. returnfalse; 77. } 78. } 79. } 80. }
回溯演算法解決:八皇后問題
八皇后問題介紹
八皇后問題,是一個古老而著名的問題,是回溯演算法的典型案例。該問題是國際西洋棋棋手馬克斯·貝瑟爾於 1848 年提出:在 8×8 格的國際象棋上擺放八個皇后,使其不能互相攻擊,即:任意兩個皇后都不能處於同一行、 同一列或同一斜線上,問有多少種擺法(答案是92種)。
八皇后問題演算法思路分析
1) 第一個皇后先放第一行第一列
2) 第二個皇后放在第二行第一列、然後判斷是否 OK, 如果不 OK,繼續放在第二列、第三列、依次把所有列都 放完,找到一個合適
3) 繼續第三個皇后,還是第一列、第二列……直到第 8 個皇后也能放在一個不衝突的位置,算是找到了一個正確 解
4) 當得到一個正確解時,在棧回退到上一個棧時,就會開始回溯,即將第一個皇后,放到第一列的所有正確解, 全部得到.
5) 然後回頭繼續第一個皇后放第二列,後面繼續迴圈執行 1,2,3,4 的步驟
- 說明:
理論上應該建立一個二維陣列來表示棋盤,但是實際上可以通過演算法,用一個一維陣列即可解決問題.
例如:arr[8] ={0 , 4, 7, 5, 2, 6, 1, 3} //對應 arr 下標表示第幾行,即第幾個皇后,arr[i] = val , val 表示第 i+1 個皇后,放在第 i+1行的第 val+1 列。
(每個棋子在放的時候,約定放的第n個棋子就僅僅在第n行找位置滿足要求)
如何解決八皇后問題?
所謂遞歸回溯,本質上是一種列舉法。這種方法從棋盤的第一行開始嘗試擺放第一個皇后,擺放成功後,遞迴一層,再遵循規則在棋盤第二行來擺放第二個皇后。如果當前位置無法擺放,則向右移動一格再次嘗試,如果擺放成功,則繼續遞迴一層,擺放第三個皇后......
如果某一層看遍了所有格子,都無法成功擺放,則回溯到上一個皇后,讓上一個皇后右移一格,再進行遞迴。如果八個皇后都擺放完畢且符合規則,那麼就得到了其中一種正確的解法。說起來有些抽象,我們來看一看遞歸回溯的詳細過程。
1.第一層遞迴,嘗試在第一行擺放第一個皇后:
2.第二層遞迴,嘗試在第二行擺放第二個皇后(前兩格被第一個皇后封鎖,只能落在第三格):
3.第三層遞迴,嘗試在第三行擺放第三個皇后(前四格被第一第二個皇后封鎖,只能落在第五格):
4.第四層遞迴,嘗試在第四行擺放第四個皇后(第一格被第二個皇后封鎖,只能落在第二格):
5.第五層遞迴,嘗試在第五行擺放第五個皇后(前三格被前面的皇后封鎖,只能落在第四格)
6.由於所有格子都“綠了”,第六行已經沒辦法擺放皇后,於是進行回溯,重新擺放第五個皇后到第八格。:
7.第六行仍然沒有辦法擺放皇后,第五行也已經嘗試遍了,於是回溯到第四行,重新擺放第四個皇后到第七格 :
8.繼續擺放第五個皇后,以此類推......
程式碼實現
1. publicclassqueen8{ 2. publicstaticfinalintMAX=8;//定義一個MAX表示共有多少個皇后 3. staticint[]arr=newint[MAX];//定義陣列array,儲存皇后放置位置的結果 4. staticintcount;//用於記錄滿足條件的陣列個數(滿足條件的擺法數) 5. 6. publicstaticvoidmain(String[]args){ 7. longstart=System.currentTimeMillis(); 8. queen8queen8=newqueen8(); 9. queen8.check(0); 10. System.out.printf("一共有%d種方案",count); 11. longstop=System.currentTimeMillis(); 12. System.out.println(stop-start); 13. 14. } 15. 16. //編寫一個方法,放置第n個皇后 17. //特別注意:check是每一次遞迴時,進入到check中都有for(inti=0;i<max;i++),因此會有回溯 18. privatevoidcheck(intn){ 19. if(n==MAX){//n=8,說明8個皇后就已經滿足擺放條件(擺放好了0到7) 20. print(); 21. return; 22. } 23. for(inti=0;i<MAX;i++){ 24. arr[n]=i;//把這個皇后放在第n行的第i列 25. //判斷當放置到第n行的第i列時是否有衝突 26. if(judge(n)){//不衝突則接著放n+1個皇后,即開始遞迴 27. check(n+1); 28. } 29. //如果衝突,就繼續執行for迴圈:array[n]=i;即:將第n個皇后,放置在本行的後移的一個位置 30. } 31. 32. } 33. 34. /** 35. *檢視當我們放置第n個皇后,就去檢測該皇后是否和前面(1到n-1)已經擺放的皇后衝突 36. * 37. *@paramn 38. *@returntrue:沒有衝突false:有衝突 39. */ 40. privatebooleanjudge(intn){ 41. for(inti=0;i<n;i++){ 42. //1.array[i]==array[n]表示判斷第n個皇后是否和前面的n-1個皇后在同一列 43. //2.Math.abs(n-i)==Math.abs(array[n]-array[i])表示判斷第n個皇后是否和第i皇后是否在同一斜線 44. //(其實就是判斷數學中兩點構成的線的斜率是否為1:abs(x1-x2)==abs(y1-y2)) 45. if(arr[n]==arr[i]||Math.abs((n-i))==Math.abs(arr[n]-arr[i])){ 46. returnfalse; 47. } 48. } 49. returntrue; 50. } 51. 52. //負責列印輸出陣列 53. publicstaticvoidprint(){ 54. count++; 55. for(intdata:arr){ 56. System.out.print(data+""); 57. } 58. System.out.println(); 59. } 60. }