java——遞歸(動態規划,回溯)


最近刷面試題經常刷到遞歸方面的算法,一直以為都是遞歸,后來發現竟然都有具體的叫法,所以寫了這篇博客來牢記以下

1. 五大常用算法

(1) 分治算法

把一個復雜的問題分成兩個或多個相同或者相似的子問題,然后不斷地細分,直到最后的子問題可以很簡單地求解出來,原問題的解就是自問題的合並。比如常見的快速排序算法和歸並算法

分治法的核心思想就是把大的難解的問題不斷分割,分而治之。

(2) 動態規划

類似於分治法,將帶求解的問題分化成幾個小問題,每個小問題的解會影響原問題的解。

先求每個子問題的局部解,然后通過決策保留那些可能達到最優解的局部解

能夠分解成若干子問題,且子問題之間有交叉

(3) 回溯算法

類似於一個枚舉的過程,其實也是一個建樹的過程,是樹的深度優先搜索。

在搜索嘗試過程中,發現當前節點已經不能滿足求解條件時,就返回其父節點,繼續嘗試別的路徑

(4) 分支界限法

類似於回溯算法,不過不是深度優先搜索,而是廣度優先搜索,一般是用到queue(先進先出)來對每一個節點進行判斷。

(5) 貪心算法

在問題求解時,總是找到局部最優解,而不是整體上最優。

貪心算法的前提:局部最優策略能最終產生全局的最優解

 

2. 遞歸(個人感覺這個學會了,算法只是如何調用遞歸)

先上一個最簡單的遞歸代碼,斐波那契數列

public class Fibo {
    public int[] array = new int[100];{
        array[0]=0;
        array[1]=1;
        array[2]=1;}
    
    public int Fibonacci(int n){
        if(n==1||n==2){
            return 1;
        }else{
            return this.array[n]=Fibonacci(n-1)+Fibonacci(n-2);
        }
        
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Fibo fb = new Fibo();
        System.out.println(fb.Fibonacci(3));

    }

}

當然,也可以來個偽遞歸,就是建立一個數組,把答案先預存起來,然后直接返回結果。劍指offer上很多直接遞歸是通不過的,必須要偽遞歸才可以

再來個青蛙跳台階的代碼,題目同樣是劍指offer上的

//一只青蛙一次可以跳上1級台階,也可以跳上2級。
//求該青蛙跳上一個n級的台階總共有多少種跳法

public class JumpFloor {
    
    public int Jump(int target){
        if(target==0){
            return 0;
        }else if(target==1){
            return 1;
        }else if(target==2){
            return 2;
        }else{
            int[] array = new int[target+1];
            array[0]=0;
            array[1]=1;
            array[2]=2;
            for(int i=3;i<array.length;i++){
                array[i]=array[i-1]+array[i-2];
            }
            return array[target];
        }
        
    }
    
    //一只青蛙一次可以跳上1級台階,也可以跳上2級,跳3個,4個......n-1個。
    //求該青蛙跳上一個n級的台階總共有多少種跳法
    public int JumpII(int target){
        if(target==0){
            return 0;
        }else if(target==1){
            return 1;
        }else if(target==2){
            return 2;
        }else{
            int[] array = new int[target+1];
            array[0]=0;
            array[1]=1;
            array[2]=2;
            int sum = 3;
            for(int i=3;i<array.length;i++){
                array[i]=sum+1;
                sum = sum+array[i];
            }
            return array[target];
        }
        
    }
    


    public static void main(String[] args) {
        // TODO Auto-generated method stub
        JumpFloor jf = new JumpFloor();
        System.out.println(jf.Jump(3));
        System.out.println(jf.JumpII(3));

    }

}

這里也是用偽遞歸來實現的

 

3. 遞歸的本質

其實遞歸的本質就是找到前后的聯系,找到遞歸的公式

例如    F(n) = F(n-1) + F(n-2) 等等

 遞歸的一般情況是

(1). if(滿足遞歸結束條件),返回值

(2). else,繼續遞歸

 

4. 回溯的本質,其實是在遞歸基礎上進行了改進

(1). if(不滿足繼續遞歸查找的條件,通常是界限判斷),返回

(2). if(滿足查找條件),記錄下來,繼續往下

(3). 加入這個節點,更新條件和值

(4). 遞歸左邊

(5).遞歸右邊

(6). 刪除這個節點(回溯)

來個例子,例子是中興的一道筆試題,我對其進行了改進。其實所有這類的能量補給,血條補給都是相同的解法。

import java.util.ArrayList;

//輸入
//補給站個數,[補給站距離],[補給站能量],目的地距離(總共需要能量),初始能量 //3, [5, 7, 10], [2, 3, 5], 15, 5 //5, [10, 20, 22, 23, 26], [10, 2, 5, 1, 1], 30, 10
//輸出
//所有可以到達的方案,到達不了返回-1
import java.util.Arrays; import java.util.Scanner; public class PowerGain { private ArrayList<ArrayList<Integer>> listAll = new ArrayList<ArrayList<Integer>>(); private ArrayList<Integer> list = new ArrayList<Integer>(); public ArrayList<ArrayList<Integer>> minNumber(int n, int[] distince, int[] judce, int left, int right, int destination, int power){ //System.out.println(left+" "+destination+" "+power); if( left>right || power<=0){ return listAll; } //if(power>=distince[left]){ list.add(left); //} if(power>=destination){ listAll.add(new ArrayList<Integer>(list)); //return listAll; } int newleft = left+1; //不喝繼續前行,喝了繼續前行 minNumber(n, distince, judce, newleft, n, destination-distince[left], power-distince[left]); minNumber(n, distince, judce, newleft, n, destination-distince[left], power-distince[left]+judce[left]); list.remove(list.size()-1); return listAll; } public void outPrint(ArrayList<ArrayList<Integer>> tempAll){ if(tempAll.size()==0){ System.out.println(-1); return; } for(int i=0;i<tempAll.size();i++){ ArrayList<Integer> temp = tempAll.get(i); for(int j=1;j<temp.size();j++){ System.out.print(temp.get(j)); if(j!=temp.size()-1){ System.out.print(" "); } } System.out.println(); } } public static void main(String[] args) { // TODO Auto-generated method stub Scanner input = new Scanner(System.in); String st = input.nextLine(); String[] sarray = st.split(", "); int n = Integer.parseInt(sarray[0]); int[] distince = new int[n+1]; int[] judce = new int[n+1]; int index = 1; //sarray下標 for(int i=0;i<n;i++){ if(i==0){ distince[i] = Integer.parseInt(sarray[index].substring(1)); judce[i] = Integer.parseInt(sarray[index+n].substring(1)); }else if(i==n-1){ distince[i] = Integer.parseInt(sarray[index].substring(0, sarray[index].length()-1)); judce[i] = Integer.parseInt(sarray[index+n].substring(0, sarray[index+n].length()-1)); }else{ distince[i] = Integer.parseInt(sarray[index]); judce[i] = Integer.parseInt(sarray[index+n]); } index++; } index = 2*n+1; int destination = Integer.parseInt(sarray[index]); int power = Integer.parseInt(sarray[index+1]); //長度分段 judce[n] = 0; distince[n]=destination-distince[n-1]; for(int i=n-1;i>0;i--){ distince[i]=distince[i]-distince[i-1]; } PowerGain m = new PowerGain(){}; ArrayList<ArrayList<Integer>> result = m.minNumber(n, distince, judce, 0, n, destination, power); m.outPrint(result); } }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM