为啥迭代置换生成器比递归慢?
Posted
技术标签:
【中文标题】为啥迭代置换生成器比递归慢?【英文标题】:Why is iterative permutation generator slower than recursive?为什么迭代置换生成器比递归慢? 【发布时间】:2013-12-12 12:47:43 【问题描述】:我正在比较两个函数用作排列生成器。这个问题是关于很多事情的:字符串实习生表,使用迭代与递归解决这个问题的利弊等等......
public static List<String> permute1(String input)
LinkedList<StringBuilder> permutations = new LinkedList<StringBuilder>();
permutations.add(new StringBuilder(""+input.charAt(0)));
for(int i = 1; i < input.length(); i++)
char c = input.charAt(i);
int size = permutations.size();
for(int k = 0; k < size ; k++)
StringBuilder permutation = permutations.removeFirst(),
next;
for(int j = 0; j < permutation.length(); j++)
next = new StringBuilder();
for(int b = 0; b < permutation.length(); next.append(permutation.charAt(b++)));
next.insert(j, c);
permutations.addLast(next);
permutation.append(c);
permutations.addLast(permutation);
List<String> formattedPermutations = new LinkedList<String>();
for(int i = 0; i < permutations.size(); formattedPermutations.add(permutations.get(i++).toString()));
return formattedPermutations;
public static List<String> permute2(String str)
return permute2("", str);
private static List<String> permute2(String prefix, String str)
int n = str.length();
List<String> permutations = new LinkedList<String>();
if (n == 0) permutations.add(prefix);
else
for (int i = 0; i < n; i++)
permutations.addAll(permute2(prefix + str.charAt(i), str.substring(0, i) + str.substring(i+1, n)));
return permutations;
我认为这两种算法通常应该是相等的,但是递归实现在 n=10 时表现良好,而 permute1(交互解决方案)在 n=8 时出现内存不足错误,其中 n 是输入字符串长度。我使用 StringBuilder 然后转换为 Strings 是一个坏主意吗?如果是这样,为什么?我认为每当您添加到一个字符串时,它都会创建一个新字符串,这会很糟糕,因为 Java 会实习它,对吧?所以你最终会得到一堆中间字符串,它们不是排列而是卡在实习生表中。
编辑:
我用 String 替换了 StringBuilder,这样就不再需要使用 StringBuilder.insert()。但是,我确实必须使用 String.substring() 来构建置换字符串,这可能不是最好的方法,但它在经验上比 StringBuilder.insert() 更好。我没有像 Alex Suo 建议的那样使用 char 数组,因为由于我的方法应该返回字符串列表,我必须将这些 char 数组转换为字符串,这会在 char 数组上引发更多垃圾收集(OutOfMemoryError 的原因)。因此,有了这个,OutOfMemoryError 和缓慢问题都得到了解决。
public static List<String> permute3(String input)
LinkedList<String> permutations = new LinkedList<String>();
permutations.add(""+input.charAt(0));
for(int i = 1; i < input.length(); i++)
char c = input.charAt(i);
int size = permutations.size();
for(int k = 0; k < size ; k++)
String permutation = permutations.removeFirst(),
next;
for(int j = 0; j < permutation.length(); j++)
next = permutation.substring(0, j + 1) + c + permutation.substring(j + 1, permutation.length());
permutations.addLast(next);
permutations.addLast(permutation + c);
return permutations;
【问题讨论】:
“交互式解决方案,在 n= 处出现内存不足错误。” ???? 递归在哪里? n = 8。递归在 permute2 方法中(倒数第三行)。 【参考方案1】:首先,由于您遇到了 OutOfMemoryError,这暗示我您正在进行大量 GC,而且众所周知,GC 是性能杀手。由于年轻一代的 GC 是世界末日,你可能会因为遭受 GC 的痛苦而获得更差的性能。
查看您的代码,如果您深入了解 StringBuilder 的实际实现,您会发现 insert() 是一项非常昂贵的操作,涉及 System.arraycopy() 等,并且可能会涉及 expandCapacity()。因为你没有提到你的 n 进行排列,所以我假设 n
如上所述,如果你真的想获得最大的性能,既然你的字符串数组的长度是预先定义的,为什么不直接使用长度 = String.length() 的 char 数组呢?就性能而言,这可能是最好的。
【讨论】:
我不知道StringBuilder的插入是这样实现的。我以为它有点像数组列表。这是很好的信息。非常感谢你的回答。我发现简单地使用字符串而不是 StringBuilder 工作得更快,并且没有 OutOfMemoryError。见编辑。 在 ArrayList 中间添加一个元素会导致 arraycopy 和潜在的确保容量。以上是关于为啥迭代置换生成器比递归慢?的主要内容,如果未能解决你的问题,请参考以下文章
Python 是不是具有用于一阶递归关系的迭代递归生成器函数?