如何在C ++中使用STL在低于总长度的位数下创建排列


15

我有一个c++ vectorstd::pair<unsigned long, unsigned long>对象。我正在尝试使用生成矢量对象的排列std::next_permutation()。但是,我希望排列具有给定的大小,类似于permutationspython中指定了预期返回排列的大小的函数。

基本上,c++相当于

import itertools

list = [1,2,3,4,5,6,7]
for permutation in itertools.permutations(list, 3):
    print(permutation)

Python演示

(1, 2, 3)                                                                                                                                                                            
(1, 2, 4)                                                                                                                                                                            
(1, 2, 5)                                                                                                                                                                            
(1, 2, 6)                                                                                                                                                                            
(1, 2, 7)                                                                                                                                                                            
(1, 3, 2)
(1, 3, 4)
..
(7, 5, 4)                                                                                                                                                                            
(7, 5, 6)                                                                                                                                                                            
(7, 6, 1)                                                                                                                                                                            
(7, 6, 2)                                                                                                                                                                            
(7, 6, 3)                                                                                                                                                                            
(7, 6, 4)                                                                                                                                                                            
(7, 6, 5) 

感谢@ Jarod42添加了该python演示程序:)
d4rk4ng31

由于我不知道python结果,不得不站在我这一边,但是可以肯定的是我知道如何在C ++中做到这一点。
Jarod42

附带说明一下,您要如何将重复输入处理为(1, 1)?python排列提供重复的[(1, 1), (1, 1)],而std::next_permutation避免重复(仅{1, 1})。
Jarod42

呃..没有 没有重复
d4rk4ng31

Answers:


6

您可以使用2个循环:

  • 取每个n元组
  • 遍历该n元组的排列
template <typename F, typename T>
void permutation(F f, std::vector<T> v, std::size_t n)
{
    std::vector<bool> bs(v.size() - n, false);
    bs.resize(v.size(), true);
    std::sort(v.begin(), v.end());

    do {
        std::vector<T> sub;
        for (std::size_t i = 0; i != bs.size(); ++i) {
            if (bs[i]) {
                sub.push_back(v[i]);
            }
        }
        do {
            f(sub);
        }
        while (std::next_permutation(sub.begin(), sub.end()));
    } while (std::next_permutation(bs.begin(), bs.end()));
}

演示版


该代码的时间复杂度是多少?一般情况下会是O(places_required * n),最坏情况下会是O(n ^ 2)吗?我也在猜测O(n)是最好的情况,即一个地方
d4rk4ng31

2
@ d4rk4ng31:我们确实只遇到了每个排列一次。的复杂度std::next_permutation是“不清楚的”,因为它计算交换(线性)。可以改进子向量的提取,但是我认为它不会改变复杂性。另外,排列的数量取决于向量的大小,因此2参数不是独立的。
Jarod42

那不是std::vector<T>& v吗?
LF

@LF:是故意的。我认为我不必更改调用方的值(我v目前正在排序)。我可能会通过const引用传递,而是在正文中创建排序的副本。
Jarod42

@ Jarod42哦,对不起,我完全误读了代码。是的,通过价值传递是正确的做法。
LF

4

如果效率不是主要考虑因素,我们可以遍历所有排列,并跳过仅选择(N - k)!第一个后缀的不同排列。例如,对于N = 4, k = 2,我们有排列:

12 34 <
12 43
13 24 <
13 42
14 23 <
14 32
21 34 <
21 43
23 14 <
23 41
24 13 <
24 31
...

我在其中插入了一个空格以使内容更清晰,并用标记了每个(N-k)! = 2! = 2排列<

std::size_t fact(std::size_t n) {
    std::size_t f = 1;
    while (n > 0)
        f *= n--;
    return f;
}

template<class It, class Fn>
void generate_permutations(It first, It last, std::size_t k, Fn fn) {
    assert(std::is_sorted(first, last));

    const std::size_t size = static_cast<std::size_t>(last - first);
    assert(k <= size);

    const std::size_t m = fact(size - k);
    std::size_t i = 0;
    do {
        if (i++ == 0)
            fn(first, first + k);
        i %= m;
    }
    while (std::next_permutation(first, last));
}

int main() {
    std::vector<int> vec{1, 2, 3, 4};
    generate_permutations(vec.begin(), vec.end(), 2, [](auto first, auto last) {
        for (; first != last; ++first)
            std::cout << *first;
        std::cout << ' ';
    });
}

输出:

12 13 14 21 23 24 31 32 34 41 42 43

3

这是一个有效的算法,它不std::next_permutation直接使用,而是利用了该函数的作用。即std::swapstd::reverse。另外,它按字典顺序排列

#include <iostream>
#include <vector>
#include <algorithm>

void nextPartialPerm(std::vector<int> &z, int n1, int m1) {

    int p1 = m1 + 1;

    while (p1 <= n1 && z[m1] >= z[p1])
        ++p1;

    if (p1 <= n1) {
        std::swap(z[p1], z[m1]);
    } else {
        std::reverse(z.begin() + m1 + 1, z.end());
        p1 = m1;

        while (z[p1 + 1] <= z[p1])
            --p1;

        int p2 = n1;

        while (z[p2] <= z[p1])
            --p2;

        std::swap(z[p1], z[p2]);
        std::reverse(z.begin() + p1 + 1, z.end());
    }
}

并称之为:

int main() {
    std::vector<int> z = {1, 2, 3, 4, 5, 6, 7};
    int m = 3;
    int n = z.size();

    const int nMinusK = n - m;
    int numPerms = 1;

    for (int i = n; i > nMinusK; --i)
        numPerms *= i;

    --numPerms;

    for (int i = 0; i < numPerms; ++i) {
        for (int j = 0; j < m; ++j)
            std::cout << z[j] << ' ';

        std::cout << std::endl;
        nextPartialPerm(z, n - 1, m - 1);
    }

    // Print last permutation
    for (int j = 0; j < m; ++j)
            std::cout << z[j] << ' ';

    std::cout << std::endl;

    return 0;
}

这是输出:

1 2 3 
1 2 4 
1 2 5 
1 2 6 
1 2 7
.
.
.
7 5 6 
7 6 1 
7 6 2 
7 6 3 
7 6 4 
7 6 5

这是来自ideone的可运行代码


2
您甚至可以通过签名进一步模仿bool nextPartialPermutation(It begin, It mid, It end)
Jarod42,


@ Jarod42,这是一个非常好的解决方案。您应该将其添加为答案...
Joseph Wood

我最初的想法是改善您的答案,但是可以。
Jarod42

3

用迭代器接口打开Joseph Wood的答案,您可能有类似以下方法的方法std::next_permutation

template <typename IT>
bool next_partial_permutation(IT beg, IT mid, IT end) {
    if (beg == mid) { return false; }
    if (mid == end) { return std::next_permutation(beg, end); }

    auto p1 = mid;

    while (p1 != end && !(*(mid - 1) < *p1))
        ++p1;

    if (p1 != end) {
        std::swap(*p1, *(mid - 1));
        return true;
    } else {
        std::reverse(mid, end);
        auto p3 = std::make_reverse_iterator(mid);

        while (p3 != std::make_reverse_iterator(beg) && !(*p3 < *(p3 - 1)))
            ++p3;

        if (p3 == std::make_reverse_iterator(beg)) {
            std::reverse(beg, end);
            return false;
        }

        auto p2 = end - 1;

        while (!(*p3 < *p2))
            --p2;

        std::swap(*p3, *p2);
        std::reverse(p3.base(), end);
        return true;
    }
}

演示版


1

经过一番思考这是我的解决方案

#include <algorithm>
#include <iostream>
#include <set>
#include <vector>

int main() {
    std::vector<int> job_list;
    std::set<std::vector<int>> permutations;
    for (unsigned long i = 0; i < 7; i++) {
        int job;
        std::cin >> job;
        job_list.push_back(job);
    }
    std::sort(job_list.begin(), job_list.end());
    std::vector<int> original_permutation = job_list;
    do {
        std::next_permutation(job_list.begin(), job_list.end());
        permutations.insert(std::vector<int>(job_list.begin(), job_list.begin() + 3));
    } while (job_list != original_permutation);

    for (auto& permutation : permutations) {
        for (auto& pair : permutation) {
            std::cout << pair << " ";
        }
        std::endl(std::cout);
    }

    return 0;
}

请评论您的想法


2
不等同于我的,它更等同于Evg的答案,(但Evg更有效地跳过重复项)。permute实际上可能只是set.insert(vec);去除一个很大的因素。
Jarod42

现在的时间复杂度是多少?
d4rk4ng31

1
我想说的O(nb_total_perm * log(nb_res))nb_total_perm其中大部分是factorial(job_list.size())nb_res结果的大小:permutations.size()),所以还是太大了。(但现在您处理与Evg相反的重复输入)
Jarod42
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.