數(shù)據(jù)結(jié)構(gòu) [Java版本] 遞歸 & 最短路徑 & 八皇后問題

遞歸的應(yīng)用場(chǎng)景

看個(gè)實(shí)際應(yīng)用場(chǎng)景,迷宮問題(回溯), 遞歸(Recursion)


遞歸
遞歸的概念

簡(jiǎn)單的說: 遞歸就是方法自己調(diào)用自己,每次調(diào)用時(shí)傳入不同的變量.遞歸有助于編程者解決復(fù)雜的問題,同時(shí)可以讓代碼變得簡(jiǎn)潔。

我列舉兩個(gè)小案例,來幫助大家理解遞歸,給大家回顧一下遞歸調(diào)用機(jī)制
打印問題
階乘問題

public class RecTest {
    public static void main(String[] args) {
        test(5);
        int factorial = factorial(10);
        System.out.println(factorial);
    }

    //輸出什么?
    public static void test(int n) {
        if (n > 2) {
            test(n - 1);
        }
        System.out.println("n=" + n);
    }

    //階乘
    public static int factorial(int n) {
        if (n == 1) {
            return 1;
        } else {
            return factorial(n - 1) * n;
        }
    }
}
遞歸調(diào)用機(jī)制的圖解
遞歸能解決什么樣的問題

各種數(shù)學(xué)問題如: 8皇后問題 , 漢諾塔, 階乘問題, 迷宮問題, 球和籃子的問題(google編程大賽)
各種算法中也會(huì)使用到遞歸,比如快排,歸并排序,二分查找陪腌,分治算法等.
將用棧解決的問題-->第歸代碼比較簡(jiǎn)潔

遞歸需要遵守的重要規(guī)則

執(zhí)行一個(gè)方法時(shí),就創(chuàng)建一個(gè)新的受保護(hù)的獨(dú)立空間(椪≡空間)
方法的局部變量是獨(dú)立的偷厦,不會(huì)相互影響, 比如n變量
如果方法中使用的是引用類型變量(比如數(shù)組),就會(huì)共享該引用類型的數(shù)據(jù).
遞歸必須向退出遞歸的條件逼近燕刻,否則就是無限遞歸,出現(xiàn)StackOverflowError只泼,死龜了:)
當(dāng)一個(gè)方法執(zhí)行完畢,或者遇到return卵洗,就會(huì)返回请唱,遵守誰調(diào)用弥咪,就將結(jié)果返回給誰,同時(shí)當(dāng)方法執(zhí)行完畢或者返回時(shí)十绑,該方法也就執(zhí)行完畢聚至。

遞歸 - 迷宮問題

說明:
小球得到的路徑,和程序員?設(shè)置的找路策略有關(guān)即:找?路的上下左右的順序相關(guān)
再得到小球路徑時(shí)本橙,可以先?使用(下右上左)扳躬,再改成(上?右下左),看看路徑是不是有變化
測(cè)試回溯現(xiàn)象
思考: 如何求出最短路徑?

代碼

package cn.icanci.datastructure.recursion;

import sun.util.resources.cldr.fr.CalendarData_fr_MQ;

/**
 * @Author: icanci
 * @ProjectName: AlgorithmAndDataStructure
 * @PackageName: cn.icanci.datastructure.recursion
 * @Date: Created in 2020/3/3 21:28
 * @ClassAction: 迷宮問題
 */
public class MiGong {
    public static void main(String[] args) {
        //先創(chuàng)建一個(gè)二維數(shù)組,模擬迷宮
        //地圖
        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;
        }
        map[3][1] = 1;
        map[3][2] = 1;
        //輸出地圖
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 7; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
        System.out.println("==========================");
        //使用遞歸給小球找路
        setWay(map, 1, 1);
        //輸出新的地圖
        for (int i = 0; i < 8; i++) {
            for (int j = 0; j < 7; j++) {
                System.out.print(map[i][j] + " ");
            }
            System.out.println();
        }
    }

    /**
     * 使用遞歸回溯來給小球找路
     * 說明 出發(fā)點(diǎn)(1,1)
     * 如果到達(dá) (6,5) 說明找到
     * 約定 大概 map[i][j] 為0的時(shí)候,沒有走過 為1是墻 2為通路可走 3為已經(jīng)走過 但是走不通
     * 在走之前 需要確定一個(gè)策略 (方法) 下->右->上->左 走不通 回溯
     *
     * @param map 表示地圖
     * @param i   從哪個(gè)位置開始找 橫坐標(biāo)
     * @param j   從哪個(gè)位置開始找 縱坐標(biāo)
     * @return 找到返回true 沒有就是false
     */
    public static boolean setWay(int[][] map, int i, int j) {
        if (map[6][5] == 2) {
            //找到
            return true;
        } else if (map[i][j] == 0) {
            //沒有走過
            //需要確定一個(gè)策略 (方法) 下->右->上->左 走不通 回溯
            //假定可以
            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, i - 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
            return false;
        }
    }
}

打印

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 1 1 1 1 1 1 
1 2 0 0 0 0 1 
1 2 2 2 0 0 1 
1 1 1 2 0 0 1 
1 0 0 2 0 0 1 
1 0 0 2 0 0 1 
1 0 0 2 2 2 1 
1 1 1 1 1 1 1 

最短路徑問題 全部思路都做一遍 然后找出最小得

遞歸 - 八皇后問題
八皇后問題介紹

八皇后問題甚亭,是一個(gè)古老而著名的問題贷币,是回溯算法的典型案例。該問題是國(guó)際西洋棋棋手馬克斯·貝瑟爾于1848年提出:在8×8格的國(guó)際象棋上擺放八個(gè)皇后亏狰,使其不能互相攻擊役纹,即:任意兩個(gè)皇后都不能處于同一行、同一列或同一斜線上暇唾,問有多少種擺法促脉。


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

1.第一個(gè)皇后先放第一行第一列
2.第二個(gè)皇后放在第二行第一列、然后判斷是否OK策州, 如果不OK瘸味,繼續(xù)放在第二列、3.第三列够挂、依次把所有列都放完硫戈,找到一個(gè)合適
繼續(xù)第三個(gè)皇后,還是第一列下硕、第二列……直到第8個(gè)皇后也能放在一個(gè)不沖突的位置,算是找到了一個(gè)正確解
4.當(dāng)?shù)玫揭粋€(gè)正確解時(shí)汁胆,在椝笮眨回退到上一個(gè)棧時(shí),就會(huì)開始回溯嫩码,即將第一個(gè)皇后誉尖,放到第一列的所有正確解,全部得到.
5.然后回頭繼續(xù)第一個(gè)皇后放第二列铸题,后面繼續(xù)循環(huán)執(zhí)行 1,2,3,4的步驟 【示意圖】

說明:理論上應(yīng)該創(chuàng)建一個(gè)二維數(shù)組來表示棋盤铡恕,但是實(shí)際上可以通過算法,用一個(gè)一維數(shù)組即可解決問題. arr[8] = {0 , 4, 7, 5, 2, 6, 1, 3} //對(duì)應(yīng)arr 下標(biāo) 表示第幾行丢间,即第幾個(gè)皇后探熔,arr[i] = val , val 表示第i+1個(gè)皇后,放在第i+1行的第val+1列

代碼實(shí)現(xiàn)

package cn.icanci.datastructure.recursion;

/**
 * @Author: icanci
 * @ProjectName: AlgorithmAndDataStructure
 * @PackageName: cn.icanci.datastructure.recursion
 * @Date: Created in 2020/3/3 22:10
 * @ClassAction: 八皇后問題
 */
public class Queue8 {
    //定義一個(gè)max表示共有多少個(gè)皇后
    int max = 8;
    //定義數(shù)組array, 保存皇后放置位置的結(jié)果,比如 arr = {0 , 4, 7, 5, 2, 6, 1, 3}
    int[] array = new int[max];
    static int count = 0;
    static int judgeCount = 0;
    public static void main(String[] args) {
        //測(cè)試一把 烘挫, 8皇后是否正確
        Queue8 queue8 = new Queue8();
        queue8.check(0);
        System.out.printf("一共有%d解法", count);
        // 1.5w
        System.out.printf("一共判斷沖突的次數(shù)%d次", judgeCount);

    }



    //編寫一個(gè)方法诀艰,放置第n個(gè)皇后
    //特別注意: check 是 每一次遞歸時(shí),進(jìn)入到check中都有  for(int i = 0; i < max; i++),因此會(huì)有回溯
    private void check(int n) {
        //n = 8 , 其實(shí)8個(gè)皇后就既然放好
        if(n == max) {
            print();
            return;
        }

        //依次放入皇后其垄,并判斷是否沖突
        for(int i = 0; i < max; i++) {
            //先把當(dāng)前這個(gè)皇后 n , 放到該行的第1列
            array[n] = i;
            //判斷當(dāng)放置第n個(gè)皇后到i列時(shí)苛蒲,是否沖突
            // 不沖突
            if(judge(n)) {
                //接著放n+1個(gè)皇后,即開始遞歸
                check(n+1); 
            }
            //如果沖突,就繼續(xù)執(zhí)行 array[n] = i; 即將第n個(gè)皇后绿满,放置在本行得 后移的一個(gè)位置
        }
    }

    //查看當(dāng)我們放置第n個(gè)皇后, 就去檢測(cè)該皇后是否和前面已經(jīng)擺放的皇后沖突
    /**
     *
     * @param n 表示第n個(gè)皇后
     * @return
     */
    private boolean judge(int n) {
        judgeCount++;
        for(int i = 0; i < n; i++) {
            // 說明
            //1. array[i] == array[n]  表示判斷 第n個(gè)皇后是否和前面的n-1個(gè)皇后在同一列
            //2. Math.abs(n-i) == Math.abs(array[n] - array[i]) 表示判斷第n個(gè)皇后是否和第i皇后是否在同一斜線
            // n = 1  放置第 2列 1 n = 1 array[1] = 1
            // Math.abs(1-0) == 1  Math.abs(array[n] - array[i]) = Math.abs(1-0) = 1
            //3. 判斷是否在同一行, 沒有必要臂外,n 每次都在遞增
            if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i]) ) {
                return false;
            }
        }
        return true;
    }

    //寫一個(gè)方法,可以將皇后擺放的位置輸出
    private void print() {
        count++;
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }
}

打印

0 4 7 5 2 6 1 3 
0 5 7 2 6 3 1 4 
0 6 3 5 7 1 4 2 
0 6 4 7 1 3 5 2 
1 3 5 7 2 0 6 4 
1 4 6 0 2 7 5 3 
1 4 6 3 0 7 5 2 
1 5 0 6 3 7 2 4 
1 5 7 2 0 3 6 4 
1 6 2 5 7 4 0 3 
1 6 4 7 0 3 5 2 
1 7 5 0 2 4 6 3 
2 0 6 4 7 1 3 5 
2 4 1 7 0 6 3 5 
2 4 1 7 5 3 6 0 
2 4 6 0 3 1 7 5 
2 4 7 3 0 6 1 5 
2 5 1 4 7 0 6 3 
2 5 1 6 0 3 7 4 
2 5 1 6 4 0 7 3 
2 5 3 0 7 4 6 1 
2 5 3 1 7 4 6 0 
2 5 7 0 3 6 4 1 
2 5 7 0 4 6 1 3 
2 5 7 1 3 0 6 4 
2 6 1 7 4 0 3 5 
2 6 1 7 5 3 0 4 
2 7 3 6 0 5 1 4 
3 0 4 7 1 6 2 5 
3 0 4 7 5 2 6 1 
3 1 4 7 5 0 2 6 
3 1 6 2 5 7 0 4 
3 1 6 2 5 7 4 0 
3 1 6 4 0 7 5 2 
3 1 7 4 6 0 2 5 
3 1 7 5 0 2 4 6 
3 5 0 4 1 7 2 6 
3 5 7 1 6 0 2 4 
3 5 7 2 0 6 4 1 
3 6 0 7 4 1 5 2 
3 6 2 7 1 4 0 5 
3 6 4 1 5 0 2 7 
3 6 4 2 0 5 7 1 
3 7 0 2 5 1 6 4 
3 7 0 4 6 1 5 2 
3 7 4 2 0 6 1 5 
4 0 3 5 7 1 6 2 
4 0 7 3 1 6 2 5 
4 0 7 5 2 6 1 3 
4 1 3 5 7 2 0 6 
4 1 3 6 2 7 5 0 
4 1 5 0 6 3 7 2 
4 1 7 0 3 6 2 5 
4 2 0 5 7 1 3 6 
4 2 0 6 1 7 5 3 
4 2 7 3 6 0 5 1 
4 6 0 2 7 5 3 1 
4 6 0 3 1 7 5 2 
4 6 1 3 7 0 2 5 
4 6 1 5 2 0 3 7 
4 6 1 5 2 0 7 3 
4 6 3 0 2 7 5 1 
4 7 3 0 2 5 1 6 
4 7 3 0 6 1 5 2 
5 0 4 1 7 2 6 3 
5 1 6 0 2 4 7 3 
5 1 6 0 3 7 4 2 
5 2 0 6 4 7 1 3 
5 2 0 7 3 1 6 4 
5 2 0 7 4 1 3 6 
5 2 4 6 0 3 1 7 
5 2 4 7 0 3 1 6 
5 2 6 1 3 7 0 4 
5 2 6 1 7 4 0 3 
5 2 6 3 0 7 1 4 
5 3 0 4 7 1 6 2 
5 3 1 7 4 6 0 2 
5 3 6 0 2 4 1 7 
5 3 6 0 7 1 4 2 
5 7 1 3 0 6 4 2 
6 0 2 7 5 3 1 4 
6 1 3 0 7 4 2 5 
6 1 5 2 0 3 7 4 
6 2 0 5 7 4 1 3 
6 2 7 1 4 0 5 3 
6 3 1 4 7 0 2 5 
6 3 1 7 5 0 2 4 
6 4 2 0 5 7 1 3 
7 1 3 0 6 4 2 5 
7 1 4 2 0 6 3 5 
7 2 0 5 1 4 6 3 
7 3 0 2 5 1 6 4 
一共有92解法一共判斷沖突的次數(shù)15720次
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末喇颁,一起剝皮案震驚了整個(gè)濱河市漏健,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌无牵,老刑警劉巖漾肮,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異茎毁,居然都是意外死亡克懊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門七蜘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谭溉,“玉大人,你說我怎么就攤上這事橡卤“缒睿” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵碧库,是天一觀的道長(zhǎng)柜与。 經(jīng)常有香客問我,道長(zhǎng)嵌灰,這世上最難降的妖魔是什么弄匕? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮沽瞭,結(jié)果婚禮上迁匠,老公的妹妹穿的比我還像新娘。我一直安慰自己驹溃,他們只是感情好城丧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著豌鹤,像睡著了一般亡哄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上布疙,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天磺平,我揣著相機(jī)與錄音魂仍,去河邊找鬼。 笑死拣挪,一個(gè)胖子當(dāng)著我的面吹牛擦酌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播菠劝,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赊舶,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了赶诊?” 一聲冷哼從身側(cè)響起笼平,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎舔痪,沒想到半個(gè)月后寓调,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锄码,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年夺英,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滋捶。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡痛悯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出重窟,到底是詐尸還是另有隱情载萌,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布巡扇,位于F島的核電站扭仁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏厅翔。R本人自食惡果不足惜斋枢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望知给。 院中可真熱鬧,春花似錦描姚、人聲如沸涩赢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筒扒。三九已至,卻和暖如春绊寻,著一層夾襖步出監(jiān)牢的瞬間花墩,已是汗流浹背悬秉。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留冰蘑,地道東北人和泌。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像祠肥,于是被迫代替她去往敵國(guó)和親武氓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容