为啥迭代置换生成器比递归慢?

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 和潜在的确保容量。

以上是关于为啥迭代置换生成器比递归慢?的主要内容,如果未能解决你的问题,请参考以下文章

在这种特殊情况下,为啥 gccgo 比 gc 慢?

为啥IE7比Safari慢?

为啥 Python 有最大递归深度?

Python 是不是具有用于一阶递归关系的迭代递归生成器函数?

为啥 __getitem__(key) 和 get(key) 比 [key] 慢很多?

迭代器生成器与递归调用