Contents

Java数组排列

1. 简介

在本文中,我们将了解如何创建数组的排列

首先,我们将定义什么是排列。其次,我们将看看一些限制条件。第三,我们将研究三种计算方法:递归、迭代和随机。

我们将专注于 Java 中的实现,因此不会涉及很多数学细节。

2. 什么是排列?

集合的排列是其元素的重新排列。由n 个元素组成的集合有n!  排列。这里n!是阶乘,它是所有小于或等于n的正整数的乘积。

2.1. 例子

整数数组 [3,4,7] 具有三个元素和六个排列:

n!= 3!= 1 x 2 x 3 = 6 排列:[3,4,7];[3,7,4]; [4,7,3];[4,3,7];[7,3,4];[7,4,3]

2.2. 约束

排列的数量随着n快速增加。虽然生成 10 个元素的所有排列只需要几秒钟,但生成 15 个元素的所有排列需要两周时间:

/uploads/java_array_permutations/1.png

3. 算法

3.1. 递归算法

我们看的第一个算法是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 void swap(T[] input, int a, int b) {
    T tmp = input[a];
    input[a] = input[b];
    input[b] = tmp;
}
private void printArray(T[] input) {
    System.out.print('\n');
    for(int i = 0; i < input.length; i++) {
        System.out.print(input[i]);
    }
}

在这里,我们将结果写入System.out,但是,我们可以轻松地将结果存储在数组或列表中。

3.2. 迭代算法

堆的算法也可以使用迭代来实现:

int[] indexes = new int[n];
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操作,因此它在数组上的效率低于堆算法。

3.4. 随机算法

如果n很大,我们可以通过打乱数组来生成随机排列

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

我们可以多次执行此操作以生成排列样本。 我们可能会多次创建相同的排列,但是,对于较大的n值,两次生成相同排列的机会很低。