生成字符串排列的复杂性

Complexity for generating permutations of a string

我想弄清楚用于生成给定字符串的所有排列的代码(来自 Cracking the Coding Interview 一书)的复杂度是 O(n!)。

我知道这是最好的复杂度,因为我们有 n!排列,但我想以代码方式理解它,因为并非每个执行此操作的算法都是 O(n!)。

代码:

import java.util.*;

public class QuestionA {

    public static ArrayList<String> getPerms(String str) {
        if (str == null) {
            return null;
        }
        ArrayList<String> permutations = new ArrayList<String>();
        if (str.length() == 0) { // base case
            permutations.add("");
            return permutations;
        }

        char first = str.charAt(0); // get the first character
        String remainder = str.substring(1); // remove the first character
        ArrayList<String> words = getPerms(remainder);
        for (String word : words) {
            for (int j = 0; j <= word.length(); j++) {
                String s = insertCharAt(word, first, j);
                permutations.add(s);
            }
        }
        return permutations;
    }

    public static String insertCharAt(String word, char c, int i) {
        String start = word.substring(0, i);
        String end = word.substring(i);
        return start + c + end;
    }

    public static void main(String[] args) {
        ArrayList<String> list = getPerms("abcde");
        System.out.println("There are " + list.size() + " permutations.");
        for (String s : list) {
            System.out.println(s);
        }
    }

}

这是我到现在为止的想法: 在任何函数调用中,可用的单词数为 (n-1) ;假设我们在余数长度为 (n-1) 的地方。现在为所有这些 (n-1) 个单词在所有可能的位置插入第 n 个元素需要 (n-1)*(n-1) 时间。

所以跨过执行,应该是(n-1)^2+(n-2)^2+(n-3)^2+....2^2+1^2次操作,我认为这不是 n!。

我错过了什么?

我觉得getPerms的时间复杂度是O((n + 1)!).

我们用T(n)表示getPerms的运行时间,其中n是输入的长度。

============================================= ======================

两个 if 分支和 char first = str.charAt(0) 行需要 O(1) 时间。下面这行需要 O(n) 时间:

String remainder = str.substring(1); // remove the first character

下一行需要时间T(n - 1):

ArrayList<String> words = getPerms(remainder);

现在我们考虑嵌套for-loops的运行时间。外for-loop的大小是(n-1)!:

for (String word : words) {

内部for-loop的大小是n + 1:

for (int j = 0; j <= word.length(); j++) {

并且insertCharAt的复杂度也是O(n)

所以嵌套for-loops的总运行时间是(n + 1) * (n - 1)! * O(n) = O((n + 1)!).

因此我们有如下关系:

T(n) = T(n - 1) + O(n) + O((n + 1)!)
     = T(n - 1) + O(n) + O((n + 1)!)
     = (T(n - 2) + O(n - 1) + O(n!) + O(n) + O((n + 1)!)
     = T(n - 2) + ( O(n - 1) + O(n) ) + ( O(n!) + O((n + 1)!) )
     = ...
     = O(n2) + (1 + ... + O(n!) + O((n + 1)!) )
     = O((n + 1)!)

如果你正在研究这个,最好学习一般的解决方案,而不是仅仅学习你的例子中给出的实现。塞奇威克做了我所知道的最好的分析。我在 class.

中教它

https://www.cs.princeton.edu/~rs/talks/perms.pdf

生成函数每次调用的复杂度为O(n)。因此成本是 O(n!).

您提供的代码效率极低。有一个巨大的常数因子,因为您正在创建大量字符串对象和数组,这是您在 Java.

中可以做的最低效的事情之一。

如果您只想遍历所有排列,排列单个实体,请不要生成列表。这是一个更快的实现:

public class Permute {
    private int[] a;
  private void swap(int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
    public Permute(int n) {
        a = new int[n];
        for (int i = 0; i < n; i++)
            a[i] = i+1;
        this.generate(n);
    }
    public void generate(int N) {
        //      System.out.println("generate(" + N + ")");
        if (N == 0) doit();
        for (int c = 0; c < N; c++) {
            //          System.out.print("Swapping: " + c + "," + N);
            swap(c, N-1);                         //swap(0, 7)
            generate(N-1);
            //          System.out.print("Swapping: " + c + "," + N);
            swap(c, N-1);
        }
    }
    public void doit() {
        for (int i = 0; i < a.length; i++)
            System.out.print(a[i] + " ");
        System.out.println();
    }

    public static void main(String[] args) {
        Permute p = new Permute(4);
    }
}

Sedgewick 展示的另一种方法是 Heaps,每个排列只有一个交换而不是 2 个。这是一个 C++ 实现:

#include <vector>
#include <iostream>

using namespace std;
class Heaps {
private:
    vector<int> p;
public:
    Heaps(int n) {
        p.reserve(n);
        for (int i = 0; i < n; i++)
            p.push_back(i+1);
        generate(n);
    }
  void doit() {
        cout << "doit size=" << p.size() << '\n';
        for (int i = 0; i < p.size(); i++)
            cout << p[i];
        cout << '\n';
    }
    void generate(int N) {
        //      cout << "generate(" << N << ")\n";
    if (N == 0)
            doit();
    for (int c = 0; c < N; c++) {
            generate(N-1);
            swap(p[N % 2 != 0 ? 0 : c], p[N-1]);
        }
    }
};


int main() {
    Heaps p(4);
}