全排列_逐步生成結果之回溯家遞迴
1.問題描述:輸入一個字串,輸出該字串的全部全排列集合
2.我們除了使用遞迴和遞推在原來的字串的基礎上新增新的字元之外,還可以使用遞迴加上回溯的方法來交換字串中字元的位置從而達到排列字串的目的,因為陣列是公共的資料空間,那麼在交換字串的位置之後那麼需要把陣列恢復到交換前的位置,那麼這就是回溯,假如不進行回溯的話那麼經過交換之後的陣列將會是亂套的
在迴圈中遞迴對比普通的單分支和雙分支的遞迴來說在理解上是比較困難的,單分支遞迴和雙分支遞迴不需要再迴圈中巢狀遞迴來實現,而多分支的遞迴則需要在迴圈中巢狀遞迴來實現,所以需要自己在紙上進行簡單例子的推理和分析來理解迴圈中的遞迴
3.下面是具體的程式碼,在迴圈中通過輸出語句來輸出相關變數的變化有助於我們理解迴圈+遞迴+回溯
import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Scanner; public class Main { //輸出結果非常有利於迴圈中遞迴的理解 static List<String> res = new ArrayList<>(); public static void main(String[] args) { Scanner sc = new Scanner(System.in); String str = sc.next(); getPermutation(str); //Collections.sort(res); System.out.println(res.size()); for(String strOutput : res){ System.out.println(strOutput); } }
private static List<String> getPermutation(String A) { char arr[] = A.toCharArray(); getPermutationCore(arr, 0); return res; }
private static void getPermutationCore(char[] arr, int k) { if(k == arr.length){ //排好了一種情況, 遞迴的一條之路已經走到底了 res.add(new String(arr)); } //從k為開始的每個字元,都嘗試放在新排列的的k這個位置 for(int i = k; i < arr.length; i++){ swap(arr, k, i); System.out.println(k +" " + i); getPermutationCore(arr, k + 1); //System.out.println(k +" "+ i+" "+arr[0]+" "+arr[1]+" "+arr[2]); //回溯: 這是核心 swap(arr, k, i);
System.out.println(arr[0]+" "+arr[1]+" "+arr[2]+" "+arr[3]); //每當一個遞迴函式碰到出口返回之後都會輸出字串的結 //果 //System.out.println(arr[0]+" "+arr[1]+" "+arr[2]); } }//交換位置 private static void swap(char[] arr, int i, int j) { char temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
下面在控制檯輸入字串ABCD,觀察控制檯的變數 i 和 k 的變化和字串排列的變化,下面是控制檯的輸出結果:
ABCD 0 0 i 和 k都等於零,A準備放在第一個位置 交換 i k 兩個位置的字元 1 1 2 2 3 3 呼叫完 getPermutationCore(arr, 3),再呼叫getPermutationCore(arr, 4)因為此時 k = 4碰到出口那麼這條支路結束進行層層的返回 (每一次i k 結果都會在碰到遞迴呼叫完成之後返回函式的時候才會輸出 而且此時已經完成了回溯) A B C D 輸出 getPermutationCore(arr, 3)呼叫完的結果 A B C D 輸出 getPermutationCore(arr, 2)呼叫完的結果 2 3 進行層層返回到k = 2 i = 2這一層之後發現有兄弟,因為原來呼叫的時候 i = k = 2 3 3 呼叫完之後還要進行for迴圈此時 i = 3 k = 2再進行遞迴下去 A B D C 輸出getPermutationCore(arr, 3)呼叫完的結果 A B C D 返回到 k = 2 i = 3的函式呼叫而且這個時候已經字串已經回溯了 A B C D 返回到 k = 1 i = 1這一層 這一層已經呼叫完了 1 2 返現有兄弟那麼進行迴圈然後遞迴呼叫此時進入下一輪迴圈中的k = 1 i = 2 然後遞迴呼叫直到碰到遞迴的出口 2 2 3 3 A C B D 呼叫完碰到出口,輸出 k = 3 i = 3呼叫完的結果 A C B D 輸出 k = 2 i = 2呼叫完的結果 2 3 返回到 k = 2 i = 2 的時候發現有兄弟繼續遞迴進入下一輪的for迴圈 3 3 A C D B 呼叫完碰到出口,輸出 k = 3 i = 3呼叫完的結果 A C B D 輸出呼叫完 k = 2 i = 1的結果 A B C D 返回到 k = 1 i = 2呼叫的這一層 1 3 返現有兄弟進入迴圈遞迴下去 2 2 3 3 A D C B A D C B 2 3 3 3 A D B C 這下面的分析與上面的一樣 A D C B A B C D A B C D 返回到k = 0 i = 0這一層表示這一層已經全部呼叫完,此時交換的陣列預警完全恢復到原來初始的狀態,那麼 0 1 下一次需要B嘗試放在開始的位置繼續遞迴下去k = 0 i = 1 直到整個大迴圈已經結束了 1 1 2 2 3 3 B A C D B A C D 2 3 3 3 B A D C B A C D B A C D 1 2 2 2 3 3 B C A D B C A D 2 3 3 3 B C D A B C A D B A C D 1 3 2 2 3 3 B D C A B D C A 2 3 3 3 B D A C B D C A B A C D A B C D 0 2 1 1 2 2 3 3 C B A D C B A D 2 3 3 3 C B D A C B A D C B A D 1 2 2 2 3 3 C A B D C A B D 2 3 3 3 C A D B C A B D C B A D 1 3 2 2 3 3 C D A B C D A B 2 3 3 3 C D B A C D A B C B A D A B C D 0 3 1 1 2 2 3 3 D B C A D B C A 2 3 3 3 D B A C D B C A D B C A 1 2 2 2 3 3 D C B A D C B A 2 3 3 3 D C A B D C B A D B C A 1 3 2 2 3 3 D A C B D A C B 2 3 3 3 D A B C D A C B D B C A A B C D 24 ABCD ABDC ACBD ACDB ADCB ADBC BACD BADC BCAD BCDA BDCA BDAC CBAD CBDA CABD CADB CDAB CDBA DBCA DBAC DCBA DCAB DACB DABC
每一次遞迴函式在交換之後然後後來返回到這一層之後都會交換回去所以最終進行下一次第二個字元想放到第一個位置上的時候該陣列還是原來的轉檯所以不會說陣列亂套了
而且遞迴碰到出口之後都會層層返回,返回到該層之後再執行遞迴函式這行程式碼下面的程式碼