【问题标题】:Fastest way to generate permutation of array of integers [duplicate]生成整数数组排列的最快方法[重复]
【发布时间】:2015-09-08 18:27:06
【问题描述】:

我想生成整数数组的所有不同排列。该数组可能包含重复项。但我想生成所有不同的排列。我尝试了下一个排列和递归方法,这些方法往往很慢。请提出建议。

【问题讨论】:

    标签: java permutation


    【解决方案1】:

    有 n! n 个元素的不同排列。生成单个排列的成本为 n(严格),因此任何排列生成算法的最小成本将是 O(n*n!)

    Steinhaus-Johnson-Trotter 算法就是其中一种算法。有像 Shimon Even 和其他算法(如 Heap)这样的改进,但没有一个能在 O(n*n!) 下得到它们

    谷歌搜索“置换算法”可以获得几种可以实现的不同算法,尽管大多数算法使用递归,这意味着另一个堆栈步骤。 Steinhaus-Johnson-Trotter 被定义为迭代,所以不应该遇到这个问题。

    这是一个 Java 实现

    import java.util.Arrays;
    import java.util.Iterator;
    
    /**
     * this implementation is based in Steinhaus–Johnson–Trotter algorithm and
     * Shimon Even's improvement;
     * 
     * @see https 
     *      ://en.wikipedia.org/wiki/Steinhaus%E2%80%93Johnson%E2%80%93Trotter_algorithm
     *
     */
    public class Permutations implements Iterator<int[]> {
        /**
         * direction[i] = -1 if the element i has to move to the left, +1 to the
         * right, 0 if it does not need to move
         */
        private int[] direction;
        /**
         * inversePermutation[i] is the position of element i in permutation; It's
         * called inverse permutation because if p2 is the inverse permutation of
         * p1, then p1 is the inverse permutation of p2
         */
        private int[] inversePermutation;
        /**
         * current permutation
         */
        private int[] permutation;
    
        /**
         * @param numElements
         *            >= 1
         */
        public Permutations(int numElements) {
            // initial permutation
            permutation = new int[numElements];
            for (int i = 0; i < numElements; i++) {
                permutation[i] = i;
            }
            // the support elements
            inversePermutation = Arrays.copyOf(permutation, numElements);
            direction = new int[numElements];
            Arrays.fill(direction, -1);
            direction[0] = 0;
        }
    
        /**
         * Swaps the elements in array at positions i1 and i2
         * 
         * @param array
         * @param i1
         * @param i2
         */
        private static void swap(int[] array, int i1, int i2) {
            int temp = array[i1];
            array[i1] = array[i2];
            array[i2] = temp;
        }
    
        /**
         * prepares permutation to be the next one to return
         */
        private void buildNextPermutation() {
            // find the largest element with a nonzero direction, and swaps it in
            // the indicated direction
            int index = -1;
            for (int i = 0; i < direction.length; i++) {
                if (direction[permutation[i]] != 0
                        && (index < 0 || permutation[index] < permutation[i])) {
                    index = i;
                }
            }
            if (index < 0) {
                // there are no more permutations
                permutation = null;
            } else {
                // element we're moving
                int chosenElement = permutation[index];
                // direction we're moving
                int dir = direction[chosenElement];
                // index2 is the new position of chosenElement
                int index2 = index + dir;
    
                // we'll swap positions elements permutation[index] and
                // permutation[index2] in permutation, to keep inversePermutation we
                // have to swap inversePermutation's elements at index
                // permutation[index] and permutation[index2]
                swap(inversePermutation, permutation[index], permutation[index2]);
                swap(permutation, index, index2);
    
                // update directions
                if (index2 == 0 || index2 == permutation.length - 1
                        || permutation[index2 + dir] > permutation[index2]) {
                    // direction of chosen element
                    direction[chosenElement] = 0;
                }
    
                // all elements greater that chosenElement set its direction to +1
                // if they're before index-1 or -1 if they're after
                for (int i = chosenElement + 1; i < direction.length; i++) {
                    if (inversePermutation[i] > index2) {
                        direction[i] = -1;
                    } else {
                        direction[i] = 1;
                    }
                }
            }
        }
    
        @Override
        public boolean hasNext() {
            return permutation != null;
        }
    
        @Override
        public int[] next() {
            int[] result = Arrays.copyOf(permutation, permutation.length);
            buildNextPermutation();
            return result;
        }
    }
    

    【讨论】:

    • 如果您使用 Steinhaus-Johnson-Trotter 置换算法重新排序现有数组,通过交换两个相邻元素(而不是生成每次迭代的新数组),那么它的最大成本为 O(N) 单个步骤,但如果您逐步遍历所有 N! 排列,则每步的平均成本为 O(1)。这给出了迭代所有排列的总时间,即O(N!)。请参阅this Thesis (page 94) 以获得证明。