2013-08-06 81 views
0

我一直在通過一本書中的示例,向您展示如何將字符串排列到所有可能的組合中,但由於我在編程方面還是一個初學者,所以我實際上無法理解代碼作品!以Java排列字符串。請解釋?

有人可以請分析我提供的代碼,並給我一個徹底的解釋,說明什麼都做,它是如何做到的?

非常感謝,

Alex。

class PermuteString{ 
    String word; 
    int index; 
    PermuteString substringGenerator; 
    public PermuteString(String s){ 
     word = s; 
     index = 0; 
     if(s.length() > 1){ 
       substringGenerator = new PermuteString(s.substring(1)); 
    } 
} 

public String nextPermutation(){ 
    if(word.length() == 1){ 
     ++index; 
     return word; 
    } 
    else{ 
     String r = word.charAt(index) + substringGenerator.nextPermutation(); 
     if(!substringGenerator.morePermutations()){ 
      ++index; 
      if(index < word.length()){ 
       String tailString = word.substring(0, index) + word.substring(index + 1); 
       substringGenerator = new PermuteString(tailString); 
      } 
     } 
     return r; 
    } 
} 

public boolean morePermutations(){ 
    return index < word.length(); 
} 

} 

public class PermuteStringDemo { 
public static void main(String[] args){ 
    PermuteString p = new PermuteString("opyn"); 
    while(p.morePermutations()) 
     System.out.println(p.nextPermutation()); 
} 
} 
+2

Java是一種清晰的聲明性語言。因此,它看起來確實如此(好吧,我們喜歡這麼想)。首先熟悉語言,您會更好地服務於您。如果您對代碼所採用的路徑感到困惑,可以嘗試一下 - 在調試器中跟蹤它,在那裏和那裏打印信息,在一張紙上寫一個字符串,然後手動應用算法,遍歷代碼並寫出一路上的字符串。 –

+0

世界上有比Java源代碼更高級別描述的空間。雖然Java非常精確,但沒有其他非Java指導,在所有樹中很難看到森林。 –

+0

這並不是因爲我對Java不熟悉 - 我比'絕對初學者'更接近'中級' - 這只是我無法理解遞歸在這個特例中如何工作。 – lukatar

回答

1

請記住,這不是排列字符串最清晰或最簡單的方法。事實上,就編程風格而言,這是無法形容的。相反,它是一個教科書示例來說明遞歸的一種形式。理解發生的最簡單的方法是逐步採取行動。

從構造函數開始。它保存單詞,將其索引設置爲0,然後(如有必要)從單詞的第2個字符到最後一個字符創建一個新的PermuteString對象。完成此操作後,您將獲得相當於PermuteString對象的鏈接列表:首先是「opyn」,然後是「pyn」,「yn」,「n」。很簡單。

現在我們來看看迭代器。 morePermutations()在標準迭代器中作爲next()的等價物;如果它的索引仍然小於它的單詞長度,它將返回true。所以,你知道當一個PermuteString索引達到它的單詞長度時,就完成了。

最後,nextPermutation()。

  • 對於一個字母的單詞,它返回它的單詞並將其索引加1。這意味着隨後對morePermutations()的調用將從此處返回false。這是有道理的:一個單字母詞本身就是一個排列組合。到現在爲止還挺好。

  • 如果N是2或更大的N字母單詞呢?在這裏,考慮一下PermuteString對象的任務:逐個返回每個字母的排列,並在沒有更多排列的情況下通知其調用者。它將這樣做的方式是將一個字母指定爲「當前」字母,並使用一個孩子PermuteString來生成其他「字母的每個可能的排列。在每次迭代中,它會首先返回一個由當前字母組成的字符串,然後是其他字母的組合。

  • 這樣做的實際情況是事情變得非常糟糕。回想一下,在構建時,每個PermuteString對象都有一個指向其第一個字母的索引和一個用於生成第二到最後一個字母的排列的子PermuteString。因此,在每次調用nextPermutation()

    • 首先,它計算並存儲在「R」的下一個排列它將返回。這只是當前的索引字母加上其他字母的下一個排列。夠簡單。

    • 然後,查看其子PermuteString,看看它是否有更多的子字符串排列給我們。如果是這樣,那麼對nextPermutation()的調用基本完成:它返回「r」並退出。

    • 但是,如果孩子的PermuteString超出了子彈,可以這麼說,那麼父母知道其當前起始字母的所有排列都已經生成。它將其索引推進到下一封信。如果它已經達到了它的結尾,那麼它的所有排列都已經耗盡;請注意,對morePermutations()的所有後續調用現在都會返回false。如果不是,那麼該對象知道它必須用它的新開始字母開始產生排列。要做到這一點,它需要創建一個全新的子字符串生成器,它由原始單詞中的所有其他字母組成 - 從位置0到剛完成的索引字母,以及從下一個字母到末尾的字母這個單詞。這就是「tailString」(一個可怕的命名變量)計算的結果,下一行爲這些其他字母創建了PermuteString置換器。

這是自修改遞歸對象的一個​​非常好的例子。爲什麼這個糟糕的真實代碼? Sheesh,有很多原因。它的變量非常傾斜地命名。它改變了substringGenerator在一個(遞歸)循環中間的值,該循環的if條件檢查它是否完成。到達結束後到nextPermutation()的調用將導致出現隨機超出界限的異常,而不是有意義的異常。等等等等。但是,遞歸本身是合乎邏輯的,並且非常值得理解它是如何工作的。乾杯!

2

要生成的排列中,通常有兩種方法

  1. 排名和Unranking
  2. 增量變化的方法

該例程使用的遞增變化的方法。基本上,它選擇元素的順序(與開始時的順序相同),然後上下移動一個選項樹,通過混洗一個元素,然後遞歸地生成其下所需的子排列。

permutation(everything) expands to 

(select 1) + permutation(everything but 1) 
(select 2) + permutation(everything but 2) 
(select 3) + permutation(everything but 3) 
... 
(select n) + permutation(everything but n) 

and 

permuation(one item) expands to 
(select item) 

這是通過morePermutations()如果只有一個在嵌套PermuteString類元件,其返回false控制。

如果PermuteString中有兩個或更多字符,index跟蹤所選項目,並且隨着索引被移動而構建新的子PermuteString

當被問及下一個排列,請求旅行下來嵌套PermuteString s,至字符串檢測它的孩子沒有「下一個」置換,這樣父更新它的指數,並互換了它以前的孩子一個新的,現在只缺少「新」index字符。

(出於完整性起見,排名和unranking的高級描述)

排名和unranking工作方式不同。人們知道特定大小的所有可能排列的數量,因此您可以爲該大小內的「索引」創建排列映射。然後,您從該索引創建一個反向映射到一個值。那麼高水平的例子看起來像

the entire map for 3 items (6 permutations) would look like 

(a, b, c) <=> 0 
(a, c, b) <=> 1 
(b, a, c) <=> 2 
(b, c, a) <=> 3 
(c, a, b) <=> 4 
(c, b, a) <=> 5 

(a, b, c) => 0 
to find the next, just add one 
0 + 1 => 1 
1 => (a, c, b) 

有對不保持在內存中的地圖繪製一個未作圖排列正式的數學手段,但他們往往不使用,因爲指數相當快的增長,往往造成問題的數字超過MAX_INT。