1. 程式人生 > 其它 >最淺顯易懂的全排列演算法Java實現

最淺顯易懂的全排列演算法Java實現

技術標籤:演算法與資料結構

對於全排列這個教科書上的入門級演算法,當初我自己可是又愛又恨,是它讓我體會到了時間倒流一樣的恐怖,又讓我油然而生一種解決問題的自豪。在這裡,我將嘗試拋開繁文縟節,從任何人都能理解的案例和思路出發,一步步踏入全排列的深坑。
(1)全排列是什麼?

其實就是針對一個數組,輸出它裡面元素可能組成的全部不重複的組合。例如:
{1,2,3} 經過全排列後得到:
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 2, 1]
[3, 1, 2]

(2)具體步驟羅列:
不著急敲程式碼實現,我們第一步都是先看看它的步驟,再看看操作有沒有共性的地方,能不能抽出來做遞迴呼叫的,所以第一步,不嫌麻煩地羅列每一步操作:
按照正常思維,我們都是先把頭部單獨出來,這樣原來陣列砍掉了頭部,可以看作是新的一個數組,這個新的陣列又可以砍掉頭部,又繼續生成新陣列,直到最後一個元素,其次,陣列元素直接肯定還要互換位置,輪流做頭部,輪流做尾部,才能充分產生每一種組合情況。
例:
陣列123

第一輪外迴圈:11交換位置(不變),首先抓住1 當頭。剩下23進入遞迴
	第一輪內迴圈
	22交換位置(不變),抓住 2 當頭,剩下3 
	輸出123

	第二輪內迴圈
	23交換位置,抓住3當頭,剩下2
	輸出132

第二輪外迴圈:12交換位置,抓住2 當頭。剩下13進入遞迴
	第一輪內迴圈
	11交換位置(不變),抓住 1
當頭,剩下3 輸出213 第二輪內迴圈 13交換位置,抓住3當頭,剩下2 輸出231 第三輪外迴圈:13交換位置,抓住3 當頭。剩下12進入遞迴 第一輪內迴圈 11交換位置(不變),抓住 1 當頭,剩下 2 輸出 312 第二輪內迴圈 12交換位置,抓住2當頭,剩下1 輸出321
根據這個詳細步驟,我們可以看到需要注意幾點:

(1)每個元素都肯定有機會輪流當老大(頭部第一位)
(2)換位置後記得換回來
(3)內迴圈和外迴圈似乎是一樣的邏輯,那就可以利用遞迴迴圈呼叫了

(3)具體步驟清晰了,嘗試著寫虛擬碼
Array = {1,2,3
}; end = Array.legth - 1; Full( Array , 0 , end) Function Full (Array , begin , end ){ if( begin == end ) { // 觸底結束,做輸出 } for( i = begin ; i <=end ; i++ ){ swap ( Array , begin , i ); Full( Array , begin+1 , end ); swap ( Array , begin , i ); } }
(4)實際程式碼:
    public static void main(String[] args) {
        int[] array = new int[]{1,2,3};
        int length = array.length;
        fullSort(array, 0, length-1);
    }
    private static void fullSort(int[] array, int begin, int end){
        // 每次遞迴迴圈的開始都咬定一個頭
        if (begin == end) {
            // 到底了就輸出
            System.out.println(Arrays.toString(array));
        } else {
            for (int i = begin; i <= end; i++) {
                // 這一步交換保證了大家輪流做一遍老大
                swap(array, begin,  i);
                // 遞迴的目的是讓除當前老大後剩下的元素列表繼續選舉老大
                fullSort(array, begin + 1, end);
                // 復位是當完老大後按照原來位置坐回去,方便重新開始選舉老大
                swap(array, begin,  i);
            }
        }
    }
    private static void swap(int[] array, int begin, int end){
        int temp = array[begin];
        array[begin] = array[end];
        array[end] = temp;
    }

結果:

在這裡插入圖片描述

(5)難點解析(個人覺得比較難理解的點)

(1)begin那個點是牢牢抓住陣列第一位下標的,而for迴圈自增的i ,就是不斷拿各個元素去跟第一位下標的元素換位置,也就是迴圈當老大。所以begin的確立很重要。
(2)遞迴中begin是當前迴圈的begin + 1 ;其實就是確立砍掉頭後的新陣列的新老大的下標位置。
(3)一定要記得遞迴for到最後一個元素完成後,復位,一步一步把調換的位置又回覆過來,不然繼續迴圈就會出現有的人做了多次老大,有的人一次都沒做過老大。

(6)生活案例

有三個小朋友(小明,小紅,小花)玩嘟嘟嘟開火車小遊戲

小明先做火車頭,小紅做二號車廂,小花做三號車廂
跑完一圈,小花對小紅說:我不想做尾巴,我想試試中間的位置,於是小花小紅換位置。
小明做火車頭,小花做二號車廂,小紅做三號車廂又跑了一圈
跑完後小花說:我體驗過了,我們換回來吧,於是小紅和小花換回了位置
小明說:火車頭太累了,下一個人當,誰第二節車廂來著?

於是小紅當上了火車頭,小明當了第二節車廂,小花還是第三
跟上面一樣,嘟嘟嘟跑完一圈,小明跟小花換個位置體驗一下互相拉衣服的感覺,又跑一圈,才換回來
小紅跑了兩圈又累了,於是跟小明說:行了我們換回來吧,好累啊
小明換回來,說:我也跑了兩圈,小花沒當過車頭,不公平
於是小明與小花換了位置,小明當了三號車廂,小花當了火車頭,嘟嘟嘟跑了一圈
第二圈,小明跟小紅換了位置,嘟嘟嘟又一圈。跑完之後又換回位置。

小花也跑完了,說:小明我們換回來吧
於是 跟開始的火車順序一樣:小明,小紅,小花。
每個人在每個位置都體驗過了,大家都公平了,於是心滿意足結束了遊戲

(7)上面舉的例子,都是以3為元素數量,是因為3最好理解,如果是5,6,7,8,9個數組元素。其實道理也都是一樣,針對陣列做迴圈,不斷砍掉頭部,產生新陣列,繼續砍掉新陣列的頭部,同時迴圈Swap反轉調換每個元素的位置,讓每個元素都有機會成為頭部被“砍頭”。
以上就是我個人總結的關於全排列的解題思路和程式碼實現