算法筆記_017:遞歸執行順序的探討(Java)


目錄

1 問題描述

2 解決方案

2.1 問題化簡

2.2 定位輸出測試

2.3 回顧總結

 

 


1 問題描述

最近兩天在思考如何使用蠻力法解決旅行商問題(此問題,說白了就是如何求解n個不同字母的所有不同排序的序列問題,即共有n!次不同排序)。

為此,我認真看了一篇出自CSDN上的博客文章,其中有一段核心代碼就是在for循環里面添加一句遞歸調用語句,來實現n!次排序。因此,我對文章中的那段核心代碼苦苦不得其解——其執行順序究竟是咋樣的呢?

附其簡要代碼:

 
         
public int count = 0;
public  void Arrange(int[] A,int start,int step,int n,int Max){
        if(step == 2)
            System.out.println("第"+(++count)+"次走完一圈");
        if(count == Max)
            System.out.println("已完成!!!");
        else{
            for(int i = start;i < n;i++){
                swapArray(A,start,i);
                Arrange(A,start+1,step+1,n,Max);
                swapArray(A,i,start);
            }
        }
    }

 

 


2 解決方案

2.1 問題化簡

剛開始學習遞歸時,課本上都會給出使用遞歸實現斐波那契數問題(PSf(1) = 1,f(2) = 1,f(3) = 2,f(4) = 3,...,f(n) = f(n-1) + f(n-2),求解f(n)),那我們就先來探討使用遞歸法求解第n個斐波那契數的具體執行順序:

先上代碼:

package com.liuzhen.chapterThree;

public class Test {
    public int Fibonacci(int n){
        System.out.println("第"+n+"次遞歸,結果:");
        if(n == 1 || n == 2)
            return 1;
        System.out.println("********");
        return Fibonacci(n-1) + Fibonacci(n-2);
    } 
    
    public static void main(String[] args){
        Test temp = new Test();
        System.out.println("運行結果:"+temp.Fibonacci(4));
    }
}

運行結果:

第4次遞歸,結果:
********
第3次遞歸,結果:
********
第2次遞歸,結果:
第1次遞歸,結果:
第2次遞歸,結果:
第4次遞歸,結果:
********
第3次遞歸,結果:
********
第2次遞歸,結果:
第1次遞歸,結果:
第2次遞歸,結果:
運行結果:3

此處是求解第4個斐波那契數,看到運行結果依次輸出數字4321243212。可以看到,輸出的數字按照第一遍輸出的樣式重復輸出了一次。

此處遞歸語句:return Fibonacci(n-1) + Fibonacci(n-2);

為此先按照語句分析如下:PS:經網上相關資料提示:遞歸算法的本質是先遞歸后回溯,從而求得最終結果。以下只是本人根據程序運行結果做出的推測分析,如有錯誤,歡迎各位圓友指正~

1Fibonacci(4) = Fibonacci(3) + Fibonacci(2),此時會首先輸出數字4

2)接着執行Fibonacci(3) = Fibonacci(2) + Fibonacci(1),此時會首先輸出數字3

3)接着執行(2)中Fibonacci2),遞歸結束,此時會首先輸出數字2

4)接着執行(2)中Fibonacci1),遞歸結束,此時會首先輸出數字1

5)接着執行(1)中Fibonacci2),遞歸結束,此時會首先輸出數字2

6)上面(1~5)步中僅僅只完成了遞歸算法,並未完成求取Fibonacci4)的具體值步驟,此時,正式開始回溯求取Fibonacci4),即Fibonacci(4) = Fibonacci(3) + Fibonacci(2),此時輸出數字4

7)回溯求取(6)中Fibonacci(3)= Fibonacci(2) + Fibonacci(1),此時輸出數字3

8)回溯求取(7)中Fibonacci(2)值,其在(3)中已獲得為1,此時輸出數字2

9)回溯求取(7)中Fibonacci(1)值,其在(4)中已獲得為1,此時輸出數字1,此時求得(6)中Fibonacci(3) = 2

10)回溯求取(6)中Fibonacci(2)值,其在(5)中已獲得為1,此時輸出數字2,此時求得6)中Fibonacci(2) = 1

11)輸出最終結果Fibonacci(4) = 2+1 =3

上述執行步驟求取Fibonacci(4)簡單示意圖:

 

 

2.2 定位輸出測試

2.1中對於斐波那契數問題的執行順序探討讓我明白了一條遞歸的實質——先遞歸后回溯。在本文的問題中,還要謹記一點——遞歸的執行順序遵循棧的特性——先進后出

先看本文所述問題具體測試代碼:

package com.liuzhen.chapterThree;

public class TravelingSalesman {
    
    public int count = 0;
    /*
     * start為開始進行排序的位置
     * step為當前正在排序的位置
     * n為需要排序的總位置數
     * Max為n!值
     */
    public  void Arrange(int[] A,int start,int step,int n,int Max){
        if(step == 2)
            System.out.println("第"+(++count)+"次走完一圈");
        if(count == Max)
            System.out.println("已完成!!!");
        else{
            System.out.println("准備進入for循環");
            for(int i = start;i < n;i++){
                swapArray(A,start,i);
                System.out.println("遞歸調用前:"+" start值為"+start+",i的值為"+i);
                Arrange(A,start+1,step+1,n,Max);
                System.out.println("遞歸調用后:"+" start值為"+start+",i的值為"+i);
                swapArray(A,i,start);
            }
        }
    }
    
    public  void swapArray(int[] A,int p,int q){
        int temp = A[p];
        A[p] = A[q];
        A[q] = temp;
    }
    
    public static void main(String[] args){
        int[] A = {0,1};
        TravelingSalesman test = new TravelingSalesman();
        test.Arrange(A,0,0,2,2);
    }
}

運行結果:

准備進入for循環
遞歸調用前: start值為0,i的值為0
准備進入for循環
遞歸調用前: start值為1,i的值為1
第1次走完一圈
准備進入for循環
遞歸調用后: start值為1,i的值為1
遞歸調用后: start值為0,i的值為0
遞歸調用前: start值為0,i的值為1
准備進入for循環
遞歸調用前: start值為1,i的值為1
第2次走完一圈
已完成!!!
遞歸調用后: start值為1,i的值為1
遞歸調用后: start值為0,i的值為1

 

此處是求取數字01的不同排序,即有2= 2種不同排序,此處依照代碼及運行結果來分析其具體執行順序:

1

准備進入for循環:

遞歸調用前: start值為0i的值為0(程序初始化值start0i等於start值為0

2

准備進入for循環:

遞歸調用前: start值為1i的值為1(執行完一次遞歸后,start值變為1i等於start值也為1

3

1次走完一圈(輸出此句,表示已經執行完兩次遞歸,start值變為了2

准備進入for循環:(此時,start值為2,即准備開始回溯,執行未完成的for循環)

遞歸調用后: start值為1i的值為1(此處,對照棧的特性,開始進行(2)中未完成的for循環,輸出此句后,將結束(2)中的for循環)

遞歸調用后: start值為0i的值為0(此處,對照棧的特性,開始進行(1)中未完成的for循環,i值將要加1

遞歸調用前: start值為0i的值為1(此處,進行了(1)中一次for循環后,就要開始執行一次遞歸語句,start值將要加1

4

准備進入for循環:

遞歸調用前: start值為1i的值為1(此處,是執行(3)中遞歸語句的執行輸出結果,此時也要開始執行一次遞歸語句,start值將要加1

5

2次走完一圈(此時,start值有變成了2

已完成!!!

遞歸調用后: start值為1i的值為1(此處,執行步驟(4)中未完成for循環的回溯,進行for循環)

遞歸調用后: start值為0i的值為1(此處,執行步驟(3)中未完成for循環的回溯,進行for循環)

 

上述是對2!次不同排序的遞歸執行順序分析,下面我們可以來看看對於3!次不同排序的遞歸執行順序結果,看看其執行順序是否滿足棧——先進后出的特性,

PS:此處對上述代碼中定義數字進行稍微修改,修改如下:

if(step == 3)
            System.out.println("第"+(++count)+"次走完一圈");


public static void main(String[] args){
        int[] A = {0,1,2};
        TravelingSalesman test = new TravelingSalesman();
        test.Arrange(A,0,0,3,6);
    }

具體運行結果:

准備進入for循環
遞歸調用前: start值為0,i的值為0
准備進入for循環
遞歸調用前: start值為1,i的值為1
准備進入for循環
遞歸調用前: start值為2,i的值為2
第1次走完一圈
准備進入for循環
遞歸調用后: start值為2,i的值為2
遞歸調用后: start值為1,i的值為1
遞歸調用前: start值為1,i的值為2(此處即將執行一次遞歸)
准備進入for循環
遞歸調用前: start值為2,i的值為2
第2次走完一圈
准備進入for循環
遞歸調用后: start值為2,i的值為2
遞歸調用后: start值為1,i的值為2
遞歸調用后: start值為0,i的值為0
遞歸調用前: start值為0,i的值為1
准備進入for循環
遞歸調用前: start值為1,i的值為1
准備進入for循環
遞歸調用前: start值為2,i的值為2
第3次走完一圈
准備進入for循環
遞歸調用后: start值為2,i的值為2
遞歸調用后: start值為1,i的值為1
遞歸調用前: start值為1,i的值為2
准備進入for循環
遞歸調用前: start值為2,i的值為2
第4次走完一圈
准備進入for循環
遞歸調用后: start值為2,i的值為2
遞歸調用后: start值為1,i的值為2
遞歸調用后: start值為0,i的值為1
遞歸調用前: start值為0,i的值為2
准備進入for循環
遞歸調用前: start值為1,i的值為1
准備進入for循環
遞歸調用前: start值為2,i的值為2
第5次走完一圈
准備進入for循環
遞歸調用后: start值為2,i的值為2
遞歸調用后: start值為1,i的值為1
遞歸調用前: start值為1,i的值為2
准備進入for循環
遞歸調用前: start值為2,i的值為2
第6次走完一圈
已完成!!!
遞歸調用后: start值為2,i的值為2
遞歸調用后: start值為1,i的值為2
遞歸調用后: start值為0,i的值為2

可以看出,運行結果明顯滿足上述中2!次運行結果的分析及推測結論。

 

2.3 回顧總結

通過以上的分析及上機輸出測試,可以初步得出遞歸算法執行順序滿足如下兩點:

(1)先執行遞歸,后進行回溯;

(2)執行順序滿足棧的特性——先進后出。

 


免責聲明!

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



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