算法之遞歸與回溯

版權聲明:本文爲博主原創文章,允許轉載,不過請標明出處。 https://blog.csdn.net/zyh2525246/article/details/79718296

遞歸
遞歸是指函數/過程/子程序在運行過程中直接或間接調用自身而產生的重入現象。

運用遞歸的條件:
1. 子問題須與原始問題爲同樣的事,且更爲簡單;
2. 不能無限制地調用本身,須有個出口,化簡爲非遞歸狀況處理。

就比如經典的漢諾塔問題:
共有3根柱子ABC,A柱上有若干個圓盤(從大到小依次擺放,最小的在最上方),大盤子不能放在小盤子的上面,只能依次移動盤子,問如何將A柱上的圓盤移動到C柱上(順序與A柱相同)?

可以將過程分爲3個步驟:
第一步:將除最大盤子以外的n-1個盤子移動到B。(可以理解爲將n-1個盤子從A移動到B,然後進行遞歸計算)
第二歩:將最大的盤子移動到C。(可以直接完成)
第三步:將B柱上的n-1個盤子移動到C。(重複前兩步,將除最大盤子以外的盤子移動到A,再將最大的盤子移動到C。)

public class 遞歸算法_漢諾塔問題 {
     public static void main(String[] args) {
    System.out.print("輸入要移動的個數:");
    Scanner scanner = newScanner(System.in);
    int num = scanner.nextInt();
    Hanoi h = new Hanoi();
    h.hanoi(num,'A','B','C');
     }
}
class Hanoi{
    void move(char a,char b){
        System.out.println(a+"----->"+b);
    }
    //n爲所需要移動盤子的個數,abc表示柱子
    void hanoi(int n, char a,char  b,char c){
        //出口
        if (n == 1) {
            move(a, c);
        }else{
            //移動除最下面以外所有盤子到b(c爲中介)
            hanoi(n - 1, a, c, b); 
            //移動最下面的盤子到c
            move(a, c);
            //移動剩下的盤子到c(在b上,a爲中介)
            hanoi(n - 1, b, a, c);

        }
    }
}

首先看一下運行結果:
這裏寫圖片描述

還有一個經典例子,四皇后問題:(沒有步驟,直接上代碼)
這裏是用一維數組存放的方法:

  1. 首先用一維數組來記錄皇后放置位置。
  2. 數組內的每個數組元素可取1—4 4個值。數字就表示皇后所放置的位置。
  3. 從第一個元素開始,取值爲1,然後進行遞歸,判斷第二個元素的取值,如果可以進行取值,則再進行遞歸,否則返回並取下一個值。
  4. 判斷條件,每個元素所取的值不能有相同的,並且2個元素之間所取值的大小不能等於數組下標的差值,即所取值不能在對角線上。
public class 遞歸算法_四皇后問題_一維數組 {
    public static void main(String[] args) {
        Squeue squeue = new Squeue();
        squeue.fun(0);      
    }
}

class Squeue{
    //存放結果的數組
    int que[] = new int[4];
    //打印最終的結果
    void display(){ 
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (j == que[i] - 1) {
                    System.out.print(1+" ");
                }else {
                    System.out.print(0+" ");
                }
            }
            System.out.println();   
        }
        System.out.println();
    }
    //出口,如果找到滿足條件的4個皇后,輸出
    void fun(int a){
        if (a == 4) {
            display();
            return;
        }
        //取值爲1--4
        for (int k = 1; k <= 4; k++) {
            boolean ct = true;
            que[a] = k;
            //判斷當前位置所放置的皇后是否滿足條件
            for (int i = 0; i < a; i++) {
                if (Math.abs(que[a]- que[i]) == a- i ) {//在對角線上
                    ct = false;
                }else if (que[a] == que[i]) {//同行
                    ct = false  ;       
                }
            }
            //如果滿足則進行遞歸,不滿足則繼續循環
            if (ct) {
                fun(a+1);
            }
        }       
    }
}

結果如下圖:
這裏寫圖片描述

回溯
回溯也稱試探法,它的基本思想是:從問題的某一種狀態(初始狀態)出發,搜索從這種狀態出發所能達到的所有“狀態”,當一條路走到“盡頭”的時候(不能再前進),再後退一步或若干步,從另一種可能“狀態”出發,繼續搜索,直到所有的“路徑”(狀態)都試探過。這種不斷“前進”、不斷“回溯”尋找解的方法,就稱作“回溯法”。

用 Icossian 問題來進行說明:

找出所有能遍歷所有的城市的路徑,且每個城市只能經過一次。
這裏寫圖片描述

由題可知有5個城市,則可設置一個5*5的數組用來存放路徑關係。用0—1表示2個城市之間是否相連。
則圖可轉變爲下列數組
這裏寫圖片描述

將行看做是出口,列看做入口。
例如從A出發可達到B,則將A所在的行內的1變爲2(關閉)。將B所在的列內的變爲2(關閉)。然後將B所在的列變成行(入口變出口 )進行遞歸,找尋下一次滿足條件的城市。如果存在則遞歸,反之跳回並將數據恢復到上一步。
當a[]數組的元素達到6個且A爲結束點時就輸出。

public class 回溯算法_Icossian問題 {
    public static void main(String[] args) {
        Country country = new Country();
        country.setCountry();
        country.visit(0, country.c);
    }
}
class Country{
    int c[][] = new int [5][5];//記錄各點之間的關係
    char a[] = new char[6];//記錄可行路徑
    int count = 1;//路徑放入的地方 a[count]
    void setCountry(){  //輸入各點之間之間的關係       a[0] = ‘A’;//從A開始
        Scanner scanner = new Scanner(System.in);
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                c[i][j] = scanner.nextInt();
            }
        }
    }

    void visit(int h, int c[][]){
        //出口  走過所有城市且最後到達A城
        if (count == 6 && a[count - 1] == 'A') {
            display(a);
            System.out.println();
        }
        //尋找當前城市與其他城市之間的通路
        for (int i = 0; i < a.length-1; i++) {
            //如果存在通路
            if (c[h][i] == 1) {
                a[count] = (char)('A' + i);
                count++;
                for (int j = 0; j < a.length-1; j++) {
                    if (c[j][i] == 1) {
                        c[j][i] = 2;//將通路關閉(表示路已經走過了)
                    }
                }
                //進行遞歸
                visit(i, c);
                //以下進行回溯,噹噹前情況遞歸完畢後,要還原當前情況下的狀態
                count--;
                for (int j = 0; j < a.length-1; j++) {
                    if (c[j][i] == 2) {
                        c[j][i] = 1;
                    }
                }
            }
        }
    }
    void display(char a[]){
        for (int i = 0; i < a.length; i++) {
            System.out.print(a[i]);
        }
    }
}

運行結果:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章