問題描述

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

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

附其簡要代碼:

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xù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);
            }
        }
    }

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

 

 


解決方案

2.1 問題化簡

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

先上代碼:

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

package com.liuzhen.chapterThree;public class Test {    public int Fibonacci(int n){
        System.out.println("第"+n+"次遞歸,結(jié)果:");        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("運行結(jié)果:"+temp.Fibonacci(4));
    }
}

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

運行結(jié)果:

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

第4次遞歸,結(jié)果:********第3次遞歸,結(jié)果:********第2次遞歸,結(jié)果:
第1次遞歸,結(jié)果:
第2次遞歸,結(jié)果:
第4次遞歸,結(jié)果:********第3次遞歸,結(jié)果:********第2次遞歸,結(jié)果:
第1次遞歸,結(jié)果:
第2次遞歸,結(jié)果:
運行結(jié)果:3

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

此處是求解第4個斐波那契數(shù),看到運行結(jié)果依次輸出數(shù)字43,2,1,2,43,2,1,2??梢钥吹?,輸出的數(shù)字按照第一遍輸出的樣式重復(fù)輸出了一次。

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

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

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

2)接著執(zhí)行Fibonacci(3) = Fibonacci(2) + Fibonacci(1),此時會首先輸出數(shù)字3;

3)接著執(zhí)行(2)中Fibonacci2),遞歸結(jié)束,此時會首先輸出數(shù)字2;

4)接著執(zhí)行(2)中Fibonacci1),遞歸結(jié)束,此時會首先輸出數(shù)字1;

5)接著執(zhí)行(1)中Fibonacci2),遞歸結(jié)束,此時會首先輸出數(shù)字2;

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

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

8)回溯求?。?/span>7)中Fibonacci(2)值,其在(3)中已獲得為1,此時輸出數(shù)字2;

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

10)回溯求?。?/span>6)中Fibonacci(2)值,其在(5)中已獲得為1,此時輸出數(shù)字2,此時求得6)中Fibonacci(2) = 1;

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

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

 Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

 

2.2 定位輸出測試

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

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

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

package com.liuzhen.chapterThree;public class TravelingSalesman {    
    public int count = 0;    /*
     * start為開始進行排序的位置
     * step為當(dāng)前正在排序的位置
     * n為需要排序的總位置數(shù)
     * 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("準(zhǔn)備進入for循環(huán)");            for(int i = start;i < n;i++){
                swapArray(A,start,i);
                System.out.println("遞歸調(diào)用前:"+" start值為"+start+",i的值為"+i);                Arrange(A,start+1,step+1,n,Max);
                System.out.println("遞歸調(diào)用后:"+" 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);
    }
}

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

運行結(jié)果:

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

準(zhǔn)備進入for循環(huán)
遞歸調(diào)用前: start值為0,i的值為0
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用前: start值為1,i的值為1
第1次走完一圈
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用后: start值為1,i的值為1
遞歸調(diào)用后: start值為0,i的值為0
遞歸調(diào)用前: start值為0,i的值為1
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用前: start值為1,i的值為1
第2次走完一圈
已完成?。?!
遞歸調(diào)用后: start值為1,i的值為1
遞歸調(diào)用后: start值為0,i的值為1

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

 

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

1

準(zhǔn)備進入for循環(huán):

遞歸調(diào)用前: start值為0,i的值為0(程序初始化值start0,i等于start值為0

2

準(zhǔn)備進入for循環(huán):

遞歸調(diào)用前: start值為1i的值為1(執(zhí)行完一次遞歸后,start值變?yōu)?/span>1i等于start值也為1

3

1次走完一圈(輸出此句,表示已經(jīng)執(zhí)行完兩次遞歸,start值變?yōu)榱?/span>2

準(zhǔn)備進入for循環(huán):(此時,start值為2,即準(zhǔn)備開始回溯,執(zhí)行未完成的for循環(huán))

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

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

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

4

準(zhǔn)備進入for循環(huán):

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

5

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

已完成?。?!

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

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

 

上述是對2!次不同排序的遞歸執(zhí)行順序分析,下面我們可以來看看對于3!次不同排序的遞歸執(zhí)行順序結(jié)果,看看其執(zhí)行順序是否滿足?!冗M后出的特性,

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

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

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);
    }

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

具體運行結(jié)果:

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

準(zhǔn)備進入for循環(huán)
遞歸調(diào)用前: start值為0,i的值為0
準(zhǔn)備進入for循環(huán)遞歸調(diào)用前: start值為1,i的值為1準(zhǔn)備進入for循環(huán)遞歸調(diào)用前: start值為2,i的值為2第1次走完一圈
準(zhǔn)備進入for循環(huán)遞歸調(diào)用后: start值為2,i的值為2遞歸調(diào)用后: start值為1,i的值為1遞歸調(diào)用前: start值為1,i的值為2(此處即將執(zhí)行一次遞歸)準(zhǔn)備進入for循環(huán)
遞歸調(diào)用前: start值為2,i的值為2
第2次走完一圈
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用后: start值為2,i的值為2
遞歸調(diào)用后: start值為1,i的值為2
遞歸調(diào)用后: start值為0,i的值為0
遞歸調(diào)用前: start值為0,i的值為1
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用前: start值為1,i的值為1
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用前: start值為2,i的值為2
第3次走完一圈
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用后: start值為2,i的值為2
遞歸調(diào)用后: start值為1,i的值為1
遞歸調(diào)用前: start值為1,i的值為2
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用前: start值為2,i的值為2
第4次走完一圈
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用后: start值為2,i的值為2
遞歸調(diào)用后: start值為1,i的值為2
遞歸調(diào)用后: start值為0,i的值為1
遞歸調(diào)用前: start值為0,i的值為2
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用前: start值為1,i的值為1
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用前: start值為2,i的值為2
第5次走完一圈
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用后: start值為2,i的值為2
遞歸調(diào)用后: start值為1,i的值為1
遞歸調(diào)用前: start值為1,i的值為2
準(zhǔn)備進入for循環(huán)
遞歸調(diào)用前: start值為2,i的值為2
第6次走完一圈
已完成?。?!
遞歸調(diào)用后: start值為2,i的值為2
遞歸調(diào)用后: start值為1,i的值為2
遞歸調(diào)用后: start值為0,i的值為2

Android培訓(xùn),安卓培訓(xùn),手機開發(fā)培訓(xùn),移動開發(fā)培訓(xùn),云培訓(xùn)培訓(xùn)

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

 

2.3 回顧總結(jié)

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

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

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

http://www.cnblogs.com/liuzhen1995/p/6368429.html