一尘不染

获取所有可能的唯一排列

algorithm

有一个带有一些符号的
数组,如['^','^','>','>','+','<','<'],我如何获得所有不同的排列?我知道有人问过类似的问题(并且已经有一些很好的答案),例如:

但是它们并没有呈现出 独特的 结果。如何仅一次 有效地 获得每个可能的结果?


阅读 186

收藏
2020-07-28

共1个答案

一尘不染

对于小数组,可以使用引用的算法之一,将每个排列映射到字符串,然后将整个数组放入Set中以丢弃重复项。就像是:

let a = ['^','^','>','>','+','<','<'];
let ps = permutations(a);  // return value should be array of arrays.
let qs = ps.map(p => p.join(""));
let s = new Set(qs);

这对于带有< 10符号的数组应该可以正常工作。

否则,请参见此处,以获取可转换为JavaScript的多种方法。

一种流行的方法是Pandita算法,它使用继承规则按字典顺序枚举排列,仅有效地生成“唯一”排列。在和此处对这种方法进行了简短说明。这是一个JavaScript(ES6)实现:

function swap(a, i, j) {
    const t = a[i];
    a[i] = a[j];
    a[j] = t;
}

function reverseSuffix(a, start) {
    if (start === 0) {
        a.reverse();
    }
    else {
        let left = start;
        let right = a.length - 1;

        while (left < right)
            swap(a, left++, right--);
    }
}

function nextPermutation(a) {
    // 1. find the largest index `i` such that a[i] < a[i + 1].
    // 2. find the largest `j` (> i) such that a[i] < a[j].
    // 3. swap a[i] with a[j].
    // 4. reverse the suffix of `a` starting at index (i + 1).
    //
    // For a more intuitive description of this algorithm, see:
    //   https://www.nayuki.io/page/next-lexicographical-permutation-algorithm
    const reversedIndices = [...Array(a.length).keys()].reverse();

    // Step #1; (note: `.slice(1)` maybe not necessary in JS?)
    const i = reversedIndices.slice(1).find(i => a[i] < a[i + 1]);

    if (i === undefined) {
        a.reverse();
        return false;
    }

    // Steps #2-4
    const j = reversedIndices.find(j => a[i] < a[j]);
    swap(a, i, j);
    reverseSuffix(a, i + 1);
    return true;
}

function* uniquePermutations(a) {
    const b = a.slice().sort();

    do {
        yield b.slice();
    } while (nextPermutation(b));
}

let a = ['^','^','>','>','+','<','<'];
let ps = Array.from(uniquePermutations(a));
let qs = ps.map(p => p.join(""));

console.log(ps.length);
console.log(new Set(qs).size);

nextPermutation函数将数组就地转换为词典序的后继者,或者转换为词典序的最小值(如果该数组已经是词典序的最大值)。在第一种情况下,它返回true,否则返回false。这使您可以循环浏览从最小(排序)数组开始的所有排列,直到nextPermutation翻转并返回false

2020-07-28