全排列是一種比較經常使用的算法。本文給出遞歸實現的兩個方法。
java
處理遞歸的時候,採用兩個字符串變量,一個存放固定前綴,一個 存放剩下的待處理的字符串。如:算法
@param prefix 固定前綴 @param valueToProcess 待處理的字符串
固定前綴prefix的初始值爲空值「」,隨着遞歸的進行不斷變化;數組
剩下的待處理元素,會隨着遞歸的進行不斷減小。函數
什麼時候輸出一個結果?測試
當剩下的待處理的字符串只有一個元素的時候,直接輸出其中一個結果。優化
格式爲:固定前綴 + 待處理的一個元素網站
private void permuteRecusion(String prefix, String valueToProcess) { int len = valueToProcess.length(); if (len == 1) { System.out.println(prefix + valueToProcess); return; } //省略其他部分 }
- 依次從剩下待處理字符元素中,選擇一個元素(好比A)與固定前綴組成一個新的固定前綴,而後與新的不包含選中元素(好比A)的待處理字符串元素一道,繼續調用遞歸函數。
- 直到剩下的待處理元素只有一個元素時,將固定前綴和該惟一待處理的元素一道輸出。
舉個例子,假設要輸出ABC的全排列,採用上述思想,輸出全排列的過程以下:spa
第一步: code
待處理的字符串爲ABC, 固定前綴爲空 ""對象
依次從ABC中選取元素,而後與前綴組成新的前綴,有以下三種狀況:
A BC , B AC , C AB (注:綠色背景的爲固定前綴,黃色背景的待處理的字符串元素)
第二步:
第一步中的三個結果,繼續調用遞歸函數,以A BC 爲例,
依次從BC中選取元素,而後與前綴(A)組成新的前綴,有以下兩種狀況:
A BC , A CB
同理,咱們能夠獲取B AC 和C AB 的結果,與A BC 的結果一共產生六個結果,包括:
A BC , A CB B AC , B CA C AB , C BA
第三步:
第二步產生的六個結果,繼續進行。由於剩下的待處理的字符串元素只有一個,因此直接輸出便可。格式爲固定前綴+待處理的字符串元素。也就獲得ABC的全排列結果:
ABC ACB BAC BCA CAB CBA
根據上述思想,咱們就能很容易地寫出遞歸方法了,如:
/** * @author wangmengjun * */ public class Permutation { /** * 根據指定的字符串,輸入全排列 * * @param value * 用於全排列的指定字符串 */ public void permutation(String value) { if (null == value || value.length() == 0) { throw new IllegalArgumentException("value不能爲空"); } permuteRecusion("", value); } /** * 遞歸處理 * * @param prefix 固定前綴 * @param valueToProcess 待處理的字符串 */ private void permuteRecusion(String prefix, String valueToProcess) { int len = valueToProcess.length(); if (len == 1) { System.out.println(prefix + valueToProcess); return; } for (int i = 0; i < len; i++) { permuteRecusion(prefix + valueToProcess.charAt(i), valueToProcess.substring(0, i) + valueToProcess.substring(i + 1, len)); } } }
測試代碼和輸出
/** * @author wangmengjun * */ public class Main { public static void main(String[] args) { Permutation example = new Permutation(); System.out.println("AB的全排列:"); example.permutation("AB"); System.out.println("ABC的全排列:"); example.permutation("ABC"); } }
AB的全排列: AB BA ABC的全排列: ABC ACB BAC BCA CAB CBA
在上述遞歸代碼中,從待處理字符串元素中選出一個元素和固定前綴時,爲了獲得不包含該選中元素的新的待處理字符串元素,代碼採用了字符串substring方法來作。
valueToProcess.substring(0, i) + valueToProcess.substring(i + 1, len)
在遞歸中,上述substring太過頻繁,不喜歡。咱們寫一個新的函數,效果與上述substring操做等效。代碼以下:
/** * 返回一個不包含指定下標元素的字符串 * @param i 須要移除元素的下標 * @param valueToProcess 用於處理的字符串 * @return */ private String populateCandidateValue(int i, String valueToProcess) { int len = valueToProcess.length(); char[] sourceValue = valueToProcess.toCharArray(); char[] destValue = new char[len - 1]; System.arraycopy(sourceValue, 0, destValue, 0, i); System.arraycopy(sourceValue, i + 1, destValue, i, destValue.length - i); return new String(destValue); }
將permuteRecusion方法中的substring代碼段替換掉。
/** * 遞歸處理 * * @param prefix 固定前綴 * @param valueToProcess 待處理的字符串 */ private void permuteRecusion(String prefix, String valueToProcess) { int len = valueToProcess.length(); if (len == 1) { System.out.println(prefix + valueToProcess); return; } for (int i = 0; i < len; i++) { permuteRecusion(prefix + valueToProcess.charAt(i), populateCandidateValue(i, valueToProcess)); } }
將上述改動,存放在一個新的文件Permutation2.java中。
/** * @author wangmengjun * */ public class Permutation2 { /** * 根據指定的字符串,輸入全排列 * * @param value * 用於全排列的指定字符串 */ public void permutation(String value) { if (null == value || value.length() == 0) { throw new IllegalArgumentException("value不能爲空"); } permuteRecusion("", value); } /** * 遞歸處理 * * @param prefix 固定前綴 * @param valueToProcess 待處理的字符串 */ private void permuteRecusion(String prefix, String valueToProcess) { int len = valueToProcess.length(); if (len == 1) { System.out.println(prefix + valueToProcess); return; } for (int i = 0; i < len; i++) { permuteRecusion(prefix + valueToProcess.charAt(i), populateCandidateValue(i, valueToProcess)); } } /** * 返回一個不包含指定下標元素的字符串 * @param i 須要移除元素的下標 * @param valueToProcess 用於處理的字符串 * @return */ private String populateCandidateValue(int i, String valueToProcess) { int len = valueToProcess.length(); char[] sourceValue = valueToProcess.toCharArray(); char[] destValue = new char[len - 1]; System.arraycopy(sourceValue, 0, destValue, 0, i); System.arraycopy(sourceValue, i + 1, destValue, i, destValue.length - i); return new String(destValue); } }
在Permutation2.java中,咱們增長了一個函數,用於返回一個不包含指定下標元素的字符串。在這個方法中,咱們先將源字符串轉換成char數組,而後經過數組複製,返回時,又將目標char數組,轉換成String來處理。
仍是不喜歡,直接使用char[]數組不就能夠了嗎?
接下來,咱們再來作一些調整,將待處理的字符串,從String改爲char[]。
修改後的代碼以下,存放在Permutation3.java中,
/** * @author wangmengjun * */ public class Permutation3 { /** * 根據指定的字符串,輸入全排列 * * @param value * 用於全排列的指定字符串 */ public void permutation(String value) { if (null == value || value.length() == 0) { throw new IllegalArgumentException("value不能爲空"); } permuteRecusion("", value.toCharArray()); } /** * 遞歸處理 * * @param prefix 固定前綴 * @param valueToProcess 待處理的字符串 */ private void permuteRecusion(String prefix, char[] valueToProcess) { int len = valueToProcess.length; if (len == 1) { System.out.println(prefix + valueToProcess[0]); return; } for (int i = 0; i < len; i++) { permuteRecusion(prefix + valueToProcess[i], populateCandidateValue(i, valueToProcess)); } } /** * 返回一個不包含指定下標元素的字符串 * @param i 須要移除元素的下標 * @param valueToProcess 用於處理的字符串 * @return */ private char[] populateCandidateValue(int i, char[] sourceValue) { int len = sourceValue.length; char[] destValue = new char[len - 1]; System.arraycopy(sourceValue, 0, destValue, 0, i); System.arraycopy(sourceValue, i + 1, destValue, i, destValue.length - i); return destValue; } }
至此,方法一的實現就所有結束了。
方法二的思想是採用交換的算法,思想來源於GeeksforGeeks.org. 網站
ABC全排列的過程以下圖所示:
根據上述思想,編寫代碼以下:
/** * * @author wangmengjun * */ public class Permute { public void permute(String value) { if (StringUtils.isEmpty(value)) { throw new IllegalArgumentException("內容不能爲空"); } int len = value.length(); permuteRecusion(value.toCharArray(), 0, len - 1); } private void permuteRecusion(char[] charValues, int begin, int end) { if (begin == end) { System.out.println(Arrays.toString(charValues)); return; } for (int i = begin; i <= end; i++) { swap(charValues, begin, i); permuteRecusion(charValues, begin + 1, end); swap(charValues, begin, i); } } private void swap(char[] charValues, int i, int j) { char temp = charValues[i]; charValues[i] = charValues[j]; charValues[j] = temp; } }
本篇博文給出了兩個遞歸實現全排列輸出的方法。其中,
方法一給出了思想,代碼實現、以及對代碼的部分優化,也算是一個不錯的編寫代碼的旅程。
方法二,如你們有興趣,能夠參考上述給出的鏈接,查看更詳細的內容。在本篇博文中就不詳細展開講了,有思路了,編寫代碼就簡單了。
方法二中,使用交換的思想,維持一個char數組,其餘經過變換來作。相對方法一,減小了不少數組拷貝或者String對象建立等,相比方法一來說更好。方法一的優點在於比較好理解。
注:如上兩種方法適合沒有重複元素的結果,若是有重複元素,還得添加額外的判斷條件進行過濾。
全排列輸出遞歸實現就寫到這裏,後期會找時間將非遞歸的實現寫上去。
如你們有較好的方法,也請告訴我一下,相互交流、相互進步~~~