1. 简介

本文将探讨如何在 Java 中生成数组的 全排列

首先,我们会定义什么是“全排列”;其次,分析一些限制条件;最后,重点介绍三种生成方式:递归法、迭代法和随机打乱法

由于我们主要关注 Java 实现,因此不会深入数学细节。

2. 什么是全排列?

一个集合的全排列指的是其元素的所有可能排列顺序。如果集合有 n 个元素,则总共有 n! 种排列方式。这里的 n! 是阶乘,即小于等于 n 的所有正整数的乘积。

2.1. 示例

以整型数组 [3,4,7] 为例:

  • 元素数量:3
  • 排列总数:3! = 1 × 2 × 3 = 6

具体排列如下:

[3,4,7]; [3,7,4]; [4,7,3]; [4,3,7]; [7,3,4]; [7,4,3]

2.2. 性能约束 ⚠️

随着 n 增大,全排列的数量增长极快。例如:

✅ 十个元素的排列只需几秒即可生成
❌ 而十五个元素则需要两周时间才能全部列出:

permutations

3. 实现算法

3.1. 递归算法(Heap 算法)

第一个介绍的是著名的 Heap 算法,它是一种递归方法,通过每次交换一个元素来生成所有排列。

⚠️ 注意:该方法会修改原数组。如果不希望改变输入数组,应在调用前创建副本。

public static <T> void printAllRecursive(
  int n, T[] elements, char delimiter) {

    if(n == 1) {
        printArray(elements, delimiter);
    } else {
        for(int i = 0; i < n-1; i++) {
            printAllRecursive(n - 1, elements, delimiter);
            if(n % 2 == 0) {
                swap(elements, i, n-1);
            } else {
                swap(elements, 0, n-1);
            }
        }
        printAllRecursive(n - 1, elements, delimiter);
    }
}

辅助方法如下:

private static void swap(T[] elements, int a, int b) {
    T tmp = elements[a];
    elements[a] = elements[b];
    elements[b] = tmp;
}
private static void printArray(T[] elements, char delimiter) {
    String delimiterSpace = delimiter + " ";
    for(int i = 0; i < elements.length; i++) {
        System.out.print(elements[i] + delimiterSpace);
    }
    System.out.print('\n');
}

📌 当前是打印到控制台,你也可以轻松地将其结果保存到 List 或数组中。

3.2. 迭代算法(Heap 算法变种)

Heap 算法同样可以使用迭代实现:

int[] indexes = new int[n];
for (int i = 0; i < n; i++) {
    indexes[i] = 0;
}

printArray(elements, delimiter);

int i = 0;
while (i < n) {
    if (indexes[i] < i) {
        swap(elements, i % 2 == 0 ?  0: indexes[i], i);
        printArray(elements, delimiter);
        indexes[i]++;
        i = 0;
    }
    else {
        indexes[i] = 0;
        i++;
    }
}

3.3. 按字典序生成排列 ✅

如果数组中的元素具有可比较性,我们可以生成按自然顺序排序的排列:

public static <T extends Comparable<T>> void printAllOrdered(
  T[] elements, char delimiter) {

    Arrays.sort(elements);
    boolean hasNext = true;

    while(hasNext) {
        printArray(elements, delimiter);
        int k = 0, l = 0;
        hasNext = false;
        for (int i = elements.length - 1; i > 0; i--) {
            if (elements[i].compareTo(elements[i - 1]) > 0) {
                k = i - 1;
                hasNext = true;
                break;
            }
        }

        for (int i = elements.length - 1; i > k; i--) {
            if (elements[i].compareTo(elements[k]) > 0) {
                l = i;
                break;
            }
        }

        swap(elements, k, l);
        Collections.reverse(Arrays.asList(elements).subList(k + 1, elements.length));
    }
}

📌 此算法在每一步都执行了 reverse 操作,因此相比 Heap 算法效率略低。

3.4. 随机排列算法 🎲

n 很大时,直接生成所有排列不现实。这时我们可以采用随机打乱的方式生成样本排列:

Collections.shuffle(Arrays.asList(elements));

你可以多次运行此方法以生成多个随机排列。

⚠️ 可能出现重复排列,但对于大 n 来说,概率非常小。

4. 小结

本文介绍了多种生成数组全排列的方法,包括递归与迭代版本的 Heap 算法,以及按字典序排序的排列生成方式和随机打乱法。

对于大规模数据,推荐使用随机生成策略。

本文章中所有代码片段均可在我们的 GitHub 仓库 中找到。


原始标题:Permutations of an Array in Java | Baeldung