选择序列中的元素

时间:2018-09-22 04:03:15

标签: algorithm combinatorics

请考虑以下问题:

我有2q个元素{a[1], ..., a[2q]}的序列,该元素已被排序。然后,我需要获得两个满足以下条件的子序列b[i]c[i]

  1. b[i]c[i]都有q元素和Join({b[i]}, {c[i]}) = {a[i]}
  2. b[1] <b[2] <... <b[q]
  3. b[i] <c[i]对于所有i = 1 ... q

困难在于我想获得满足条件的所有所有序列。我该怎么办?

1 个答案:

答案 0 :(得分:2)

第一步是将输入分为两半,同时确保每个分区都会导致一个解决方案。我们可以使用简单的递归方法来做到这一点:

从小到大依次遍历输入;对于每个元素,使用以下规则选择是进入子序列b还是进入子序列c:

  • 如果b和c的长度相同,则将元素添加到b。
  • 如果b长于c,则将元素一次添加到b,一次添加到c,然后使用这两个选项递归。
  • 如果b已满,则将所有元素添加到c。

如果有不同的分区,例如对于输入[1,2,3,4,5,6],它们将是:

[1,2,3],[4,5,6]
[1,2,4],[3,5,6]
[1,2,5],[3,4,6]
[1,3,4],[2,5,6]
[1,3,5],[2,4,6]

然后,对于每个这些分区,我们必须找到满足条件的c的排列。同样,简单的递归方法可以解决问题:

从右到左遍历b。对于每个元素,在c中找到更大的元素。如果只有一个,请将其放在c中的相应位置。如果严重,则将每个元素放置在c中的相应位置一次,然后使用每个选项递归。

对于示例中的分区[1,2,4],[3,5,6],将导致:

[1,2,4],[x,x,x] <- (3,5,6) ... [1,2,4],[x,x,6] <- (3,5) ... [1,2,4],[3,5,6]
     ^       ^          ^         ^       ^          ^
                           ... [1,2,4],[x,x,6] <- (3,5) ... [1,2,4],[5,3,6]
                                  ^       ^        ^
[1,2,4],[x,x,x] <- (3,5,6) ... [1,2,4],[x,x,5] <- (3,6) ... [1,2,4],[3,6,5]
     ^       ^        ^           ^       ^          ^
                           ... [1,2,4],[x,x,5] <- (3,6) ... [1,2,4],[6,3,5]
                                  ^       ^        ^

此代码示例是上述算法的直接实现:

function subsequences(a) {
    var q = a.length / 2;
    partition(0, [], []);

    function partition(pos, part1, part2) {
        var b = part1.slice();                  // create copy
        var c = part2.slice();                  // create copy
        if (b.length == c.length) {
            b.push(a[pos++]);
        }
        while (b.length < q) {
            c.push(a[pos++]);
            partition(pos, b, c);
            b.push(c.pop());
        }
        while (c.length < q) {
            c.push(a[pos++]);
        }
        permute(b, [], c);
    }

    function permute(b, part, set) {
        var pos = set.length - 1;
        for (var i = pos; i >= 0 && set[i] > b[pos]; i--) {
            var c = part.slice();               // create copy
            var s = set.slice();                // create copy
            c[pos] = s.splice(i, 1);
            if (pos == 0) {   // store or process subsequences
                document.write("{" + b + "},{" + c + "}<br>");
            }
            else permute(b, c, s);
        }
    }
}
subsequences([1,2,3,4,5,6,7,8,9,10,11,12]); // ascending order

该算法找到以下数量的有效分区以及c的有效排列。 (为了进行比较,我添加了天真的解决方案需要生成然后检查的所有分区和所有排列的数量。)

 q  partitions     permutations     |   all part.        all perm.
                                    |
 1           1                1     |          1                1
 2           2                3     |          3                6
 3           5               15     |         10               60
 4          14              105     |         35              840
 5          42              945     |        126           15,120
 6         132           10,395     |        462          332,640
 7         429          135,135     |      1,716        8,648,640
 8       1,430        2,027,025     |      6,435      259,459,200
 9       4,862       34,459,425     |     24,310    8,821,612,800
10      16,796      654,729,075     |     92,378  335,221,286,400

仅运行算法的第一部分,我们发现对于q = 20,有效分区数为6,564,120,420,有效排列数可能约为10 22


注意:

为使分区功能正常工作,输入顺序必须按升序排列。在置换功能中,子序列c中未使用的值的集合也按升序保留,以便更轻松地找到最大值。

任何n个数字输入序列的结果都将相似。实际上,您可以将输入序列替换为[0,1,2,3 ... n-1],然后将结果中的值用作原始输入序列中的索引。这意味着,如果需要n个数字的不同序列的结果,则只需运行一次算法。