我一直在编写一本书中的示例,向您展示如何将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 :(得分: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
传播,直到一个字符串检测到它的子节点没有“下一个”排列,因此父节点更新它的索引,并交换它的以前的孩子有一个新的孩子现在只缺少“新”index
字符。
(为了完整起见,排名和取消排名的高级描述)
排名和取消工作的方式不同。人们知道特定大小的所有可能排列的计数,因此您可以在该大小内创建一个排列映射到“索引”。然后,您将从该索引创建一个反向映射到一个值。然后高级示例看起来像
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时产生问题。
答案 1 :(得分:1)
请记住,这不是置换字符串的最清晰或最简单的方法。事实上,就编程风格而言,它是令人难以置信的悲惨。相反,它是一个教科书示例来说明一种递归形式。了解正在发生的事情的最简单方法是逐步采取措施。
从构造函数开始。它保存单词,将其索引设置为0,然后(如果需要)从单词的第2个字符到最后一个字符创建一个新的PermuteString对象。完成此操作后,您将获得PermuteString对象的链接列表:首先是“opyn”,然后是“pyn”,“yn”,“n”。很容易。
现在让我们看一下迭代器。 morePermutations()在标准迭代器中用作next()的等价物;如果索引的索引仍然小于其单词的长度,则返回true。所以,你知道当PermuteString索引达到它的单词长度时,就完成了。
最后,nextPermutation()。
对于单字母单词,它会返回其单词并将1加到其索引中。这意味着对morePermutations()的后续调用将从此处开始返回false。这是有道理的:一个字母的单词只有一个排列 - 本身。到目前为止一切顺利。
N字母是多少?N是2还是更多?在这里,考虑一下PermuteString对象的任务:逐个返回其字母的每个排列,并在不再存在排列时通知其调用者。这样做的方法是将一个字母指定为“当前”字母,并使用子PermuteString生成其“字母”的每个可能的排列。在每次迭代中,它首先返回一个由当前字母组成的字符串,然后是其他字母的某种组合。
实际上这是怎么回事,事情变得非常糟糕。回想一下,在构造时,每个PermuteString对象都有一个指向其第一个字母的索引加上一个子PermuteString,用于生成其第二个字母到第二个字母的排列。因此,每次调用nextPermutation()
首先,它计算并存储“r”它将返回的下一个排列。这只是当前的索引字母加上其他'其他'字母的下一个排列。很简单。
然后,它会看一下它的子PermuteString,看它是否有更多的子串排列给我们。如果是,那么对nextPermutation()的调用基本上完成了:它返回“r”并退出。
但是,如果孩子PermuteString没有子弹,那么父母就知道已经生成了当前起始字母的所有排列。它将其索引推进到下一个字母。如果它已经达到了它的结尾,那么它的所有排列都已经用完了;请注意,对morePermutations()的所有后续调用现在都将返回false。如果没有,那么该对象知道它必须开始用其新的起始字母生成排列。要做到这一点,它需要创建一个全新的子字符串生成器,其中包含原始单词中的所有其他字母 - 从位置0到刚刚结束的索引字母,再加上下一个字母。写到这个词的结尾。这就是“tailString”(一个可怕的命名变量)计算,下一行为其他字母创建PermuteString置换器。
这是自修改递归对象的一个很好的例子。为什么这个糟糕的真实代码? Sheesh,有很多原因。它的变量非常倾斜。它在(递归)循环的中间更改substringGenerator的值,其if条件检查是否完成。在达到结束后调用nextPermutation()将导致随机的越界异常,而不是有意义的异常。等等等等。但是,递归本身是合乎逻辑的,并且值得理解它是如何工作的。干杯!