1. 程式人生 > 實用技巧 >遞迴思想與典型問題淺析

遞迴思想與典型問題淺析

遞迴需要遵守的重要規則

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. }