我所知道的算法之遞歸

做者前言

你們好,我是阿濠,今篇內容跟你們分享的是算法之遞歸,很高興分享到segmentfault與你們一塊兒學習交流,初次見面請你們多多關照,一塊兒學習進步.

1、遞歸的概念

簡單的來講,遞歸就是本身調用本身,每次調用時傳入不一樣的變量,遞歸有助於編程者解決複雜的問題,同時讓本身代碼變成簡潔算法

2、遞歸的調用機制

列舉兩個小案例,認識遞歸調用機制編程

1.打印問題、2.階乘問題segmentfault

//1.打印問題
public class RecursionTest {
    public static void main(String[] args) {
        //經過打印問題,回顧遞歸調用機制
        test(4);
    }
    public static void test(int n) {
        if (n > 2) {
            test(n - 1);
        }
        System.out.println("n=" + n);
    }
}

遞歸在咱們的程序內部如何調用的?程序從main方法入口進入數組

當咱們進入main方法調用test(4)時,開闢獨立空間學習

圖片.png

執行test(4)方法時,會有變量n = 4 並進行if判斷(4>2),執行test(4-1)方法測試

圖片.png

此時執行test(3)方法時,n = 3 也進行if判斷(3>2),並執行test(3-1)方法優化

圖片.png

此時執行test(2)方法時,n = 2 也進行if判斷(2>2),可是2>2 不成立,執行輸出指令網站

圖片.png

此時test(2)執行完畢後控制檯進行輸出的結果是 n =2 當test(2)執行完後就退到test(3).. n=三、test(4)... n=4ui

圖片.png

圖片.png

public class RecursionTest {
    public static void main(String[] args) {
        int res=factorial(3);
        System.out.println("結果="+res);
    }
    //階乘問題
    public static int factorial(int n) {
        if(n==1){
            return 1;
        } else {
            return factorial(n - 1) * n;
        }
    }
}

按照剛剛示意圖的理解,就先是factorial(3 - 1 ) * 3factorial(2 - 1 ) * 2、而factorial(1)直接return 1,因此輸出結果就是1 * 2 * 3=6 spa

遞歸機制小結:

1.當程序執行到一個方法時,就會開闢獨立的空間(棧)
2.每一個空間的數據(局部變量),是獨立的

遞歸能夠解決什麼問題呢?

1.各類數學問題:
如:8皇后問題,漢諾塔,階乘問題,迷宮問題,球和籃子的問題等等

2.各類算法中也會使用到遞歸
好比快排,歸併排序,二分查找,分治算法等等.

3.將用棧解決的問題-->第歸代碼比較簡潔

遞歸須要遵照的重要規則

1)執行一個方法時,就建立一個新的受保護的獨立空間(棧空間)

2)方法的局部變量是獨立的不會相互影響,好比以前舉例的 n 變量

3)若是方法中是使用引用變量,即會共享引用類型的數據,即迷宮問題時方法傳遞是數組,數組是引用類型那麼就會進行調用同一個數據:數組

4)遞歸必須向退出遞歸的條件逼近,不然就是無限遞歸,死龜了拋出異常:StackOverflowError

5)當一個方法執行完畢,或者遇到return, 就會返回遵照誰調用,就將結果返回給誰,同時當方法執行完畢或者返回時,該方法也就執行完畢。

遞歸的實際問題案例

遞歸-迷宮問題

圖片.png

思路分析:

1.建立二維數組模擬迷宮
2.使用1表明牆,上下全是1,便是列在變,行沒變,因此範圍是0-6在變
3.使用1表明牆,左右全是1,便是行在變,列沒變,因此範圍是0-7在變
4.使用1表明擋板,即小球沒法走到擋板的位置,按圖所示第四行的一、2列
5.約定:0 表明沒有走過、1 表示牆、2表示路能夠走、3已走過但不通
6.按圖所示,起點爲止從(1,1)開始(第二行第二列),小球在(6,5)的位置
7.若小球爲(i,j),向左走是(i,j-1),向右走是(i,j+1),向上走是(i-1,j),向下是(i+1,j)

建立迷宮

//先建立一個二維數組,模擬迷宮地圖
int[][] map = new int[8][7];
//使用1表示牆
//上下所有置爲1
for (int i = 0; i < 7; i++) {
    map[0][i] = 1;
    map[7][i] = 1;
}
//左右所有置爲1
for (int i = 0; i < 8; i++) {
    map[i][0] = 1;
    map[i][6] = 1;
}

//設置擋板, 1表示
map[3][1] = 1;
map[3][2] = 1;

//輸出地圖
System.out.println("地圖的狀況");
for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 7; j++) {
        System.out.print(map[i][j] + " ");
    }
    System.out.println();
}

運行結果:
1 1 1 1 1 1 1 
1 0 0 0 0 0 1 
1 0 0 0 0 0 1 
1 1 1 0 0 0 1 
1 0 0 0 0 0 1 
1 0 0 0 0 0 1 
1 0 0 0 0 0 1 
1 1 1 1 1 1 1

找小球策略:下右上左

//使用遞歸回溯來給小球找路
//說明
//1. map表示地圖
//2. i,j 表示從地圖的哪一個位置開始出發(1,1)
//3. 若是小球能到map[6][5]位置,則說明通路找到. .
//4. 約定:當map[i][j] 爲0表示該點沒有走過當爲1表示牆; 2表示通路能夠走; 3表示該點已經走過,可是走不通
//5. 在走迷宮時,須要肯定-個策略(方法):下->右->上->左 ,若是該點走不通,再回溯
/**
 * @param map 表示地圖
 * @param i 從哪一個位置開始找
 * @param j 從哪一個位置開始找
 * @return 若是找到通路,就返回true, 不然返回false
 */
public static boolean setWay(int[][] map, int i, int j) {
    //小球的位置是(6,5) 對應數組則是map[6][5]
    if(map[6][5] == 2) {
        //到map[6][5]位置,說明球已找到
        return true;
    } else {
        //判斷到地圖map[i][j]是0,則表明這個點尚未走過
        if (map[i][j] == 0) {
            //假設這個點是能夠走通的,而後按照策略:下一>右->上->左走
            map[i][j] = 2;
            if (setWay(map, i + 1, j)) {//向下走
                return true;
            } else if (setWay(map, i, j + 1)) { //向右走
                return true;
            } else if (setWay(map, 1 - 1, j)) { //向上
                return true;
            } else if (setWay(map, i, j - 1)) { //向左走
                return true;
            }else {
                //說明該點是走不通,已經走過,是死路
                map[i][j] =3;
                return false;
            }
        } else{
            //若是map[i][j] != 0 ,多是1, 2, 3
            //當爲1表示牆; 2表示通路能夠走; 3表示該點已經走過
            return false;
        }
    }
}

步驟分析圖:

圖片.png

圖片.png

圖片.png

圖片.png

圖片.png

圖片.png

.....

遞歸-八皇后問題

八皇后問題,是一個古老而著名的問題,是回溯算法的典型案例。該問題是國際西洋棋棋手馬克斯●貝瑟爾於1848年提出:在8X8格的國際象棋上擺放八個皇后,使其不能互相攻擊,即:任意兩個皇后都不能處於同一行、同一列或同一斜線上,問有多少種擺法(92)

八皇后問題算法思路分析.

1.第一個皇后先放第一行第一列
2.第二個皇后放在第二行第一列,而後判斷是否0K,若是不OK繼續放在第二列、第三列...依次把全部列都放完,找到合適的位置
3.繼續第三個皇后仍是第一列、第二列...`直到第八個皇后都放在一個不衝突的位置,算是找到了一個正確解`
4)當獲得一個正確解時,棧回退到上一個棧開始回溯,即第一個皇后放到第一列的全部正確解所有獲得.
5)而後回頭繼續把第一個皇后第二列,後面繼續循環執行1,2,3的步驟

步驟示意圖分析

圖片.png

按照思路先將.第一個皇后先放第一行第一列 (小圓圈表明皇后)

圖片.png

第二個皇后放在第二行第一列,而後判斷是否0K,顯然條件是:兩個皇后都不能處於同一行、同一列或同一斜線上

圖片.png

圖片.png

因而第二個皇后再向右移動一個位置,知足條件不是相互攻擊

圖片.png

因而繼續第三個皇后仍是第一列開始,顯然條件是:兩個皇后都不能處於同一行、同一列或同一斜線上...第...皇后

圖片.png

圖片.png

圖片.png

圖片.png

圖片.png

圖片.png

這時獲得一個正確的解就會進行把第一個皇后第二列,後面繼續循環執行1,2,3的步驟,可是效率並不高,由於8x8的會執行一萬屢次,後面會有算法進行優化

提示說明:

理論上應該建立一個二維數組來表示棋盤,可是實際上能夠經過算法用一個一維數組便可解決問題:arr[8]= {0,4, 7,5,2, 6, 1, 3}

arr下標表示第幾行,即第幾個皇后,arr[i] =val, val表示第i+1個皇后,放在第i+1行的第i+1列

即咱們使用下標表示第幾行是第幾個皇后,按圖所示那麼對應的值如果一致則表明在同一列:array[n]==array[i]

圖片.png

即咱們使用下標表示第幾行是第幾個皇后,那麼第n個皇后與第n-1皇后的列差 == 第n個皇后與第n-1行差,那麼則是同一斜線上:Math.abs(n - i) == Math.abs(array[n] - array[i]),好比說第二個皇后與第一個皇后的行差=一、列差=1,相等證實按圖所示它們是斜線

圖片.png

實際操做代碼以下:

public class Queue8 {

    //定義一個max表示共有多少個皇后
    int max = 8;
    //定義數組array,保存皇后放置位置的結果,好比arr = {0 , 4, 7, 5, 2, 6, 1, 3}
    int[] array = new int[max];
    
    //放置第n個皇后
    public void check(int n){
        //從0開始放置 若n=8 則表明最後一列的皇后了
        if( n == max ){
            printQueue();
            return;
        }
        //依次放入皇后
        for(int i=0; i<max; i++){
            //把當前皇后n,放入第一列位置
            array[n]=i;
            //放入第n個皇后檢查是否衝突
            if(judge(n)){
                //若不衝突,則執行n+1個皇后
                check(n+1);
            }
            //若衝突 則繼續執行arr[n] = i;即將第n個皇后 放置在本行的後移一個位置
        }

    }

    //查看當咱們放置第n個皇后,就去檢測該皇后是否和前面已經擺放的皇后衝突
    /**
     * @param n 表示第n個皇后
     * @return
     */
    private boolean judge(int n) {

        for(int i=0; i<n; i++) {
            //1. array[i] == array[n] 表示判斷 第n個皇后是否和前面的n-1個皇后在同一列
            //2. Math.abs(n-i) == Math.abs(array[n] - array[i]) 表示判斷第n個皇后是否和第i皇后是否在同一斜線
            //行差=列差 證實是斜線
            if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {
                return false;
            }
        }
        return true;
    }

    //寫一個方法,能夠將皇后擺放的位置輸出
    private void printQueue(){

        for (int i = 0; i < array.length; i++) {
            System. out. print(array[i] +"");
        }
        System.out.println();
    }
}

按照思路,添加數據測試一下把

public static void main(String[] args) {
    //測試八皇后
    Queue8 queue8 = new Queue8();
    queue8.check(0);
}

運行結果以下:
04752613
05726314
06357142
06471352
13572064
14602753
14630752
15063724
15720364
16257403
16470352
17502463
20647135
24170635
24175360
24603175
24730615
25147063
25160374
25164073
25307461
25317460
25703641
25704613
25713064
26174035
26175304
27360514
30471625
30475261
31475026
31625704
31625740
31640752
31746025
31750246
35041726
35716024
35720641
36074152
36271405
36415027
36420571
37025164
37046152
37420615
40357162
40731625
40752613
41357206
41362750
41506372
41703625
42057136
42061753
42736051
46027531
46031752
46137025
46152037
46152073
46302751
47302516
47306152
50417263
51602473
51603742
52064713
52073164
52074136
52460317
52470316
52613704
52617403
52630714
53047162
53174602
53602417
53607142
57130642
60275314
61307425
61520374
62057413
62714053
63147025
63175024
64205713
71306425
71420635
72051463
73025164

總共是92種思路解法,發現就是按照思路圖的思路進行遞歸判斷,可進入小遊戲網站實踐一下:http://www.4399.com/flash/426...

相關文章
相關標籤/搜索