我一直在阅读一本书中的示例,该示例向您展示如何将String置换为所有可能的组合,但是由于我还是一般的编程初学者,因此我实际上无法理解代码的工作原理!
有人可以分析一下我提供的代码,并对所有功能及其执行方式进行详尽的解释吗?
非常感谢,
亚历克斯
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());
}
}
最佳答案
请记住,这不是置换字符串的最清晰或最简单的方法。实际上,就编程风格而言,它简直是可悲的。而是一个教科书示例来说明递归的形式。理解正在发生的事情的最简单方法是一步一步地进行。
从构造函数开始。它保存单词,将其索引设置为0,然后(如有必要)从单词的第二个字符到最后一个字符创建一个新的PermuteString对象。完成此操作后,您将具有相当于PermuteString对象的链表的功能:首先是“ opyn”,然后是“ pyn”,“ yn”,“ n”。很简单。
现在让我们看一下迭代器。 morePermutations()与标准Iterator中的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置换符。
这是自我修改递归对象的一个很好的例子。为什么这个糟糕的实词代码?嘘,有很多原因。其变量的名称非常倾斜。它在(递归)循环的中间(如果条件检查是否已完成)更改substringGenerator的值。到达末尾后再调用nextPermutation()将导致随机越界异常,而不是有意义的异常。等等等等。但是,递归本身是合乎逻辑的,值得理解它是如何工作的。干杯!