Cách tạo hoán vị trong c ++ bằng STL cho số vị trí thấp hơn tổng chiều dài


15

Tôi có một c++ vectorvới std::pair<unsigned long, unsigned long>các đối tượng. Tôi đang cố gắng tạo ra hoán vị của các đối tượng của vectơ bằng cách sử dụng std::next_permutation(). Tuy nhiên, tôi muốn các hoán vị có kích thước nhất định, bạn biết, tương tự như permutationshàm trong python nơi kích thước của hoán vị trả về dự kiến ​​được chỉ định.

Về cơ bản, c++tương đương với

import itertools

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

Trình diễn 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) 

Cảm ơn @ Jarod42 đã thêm bản demo python đó :)
d4rk4ng31

Phải làm điều đó về phía tôi, vì tôi không biết kết quả python, nhưng khá chắc chắn rằng tôi biết cách làm điều đó trong C ++.
Jarod42

Là một lưu ý phụ, bạn muốn xử lý các đầu vào trùng lặp như (1, 1)thế nào? hoán vị python cung cấp trùng lặp [(1, 1), (1, 1)], trong khi std::next_permutationtránh trùng lặp (chỉ {1, 1}).
Jarod42

À .. không. Không trùng lặp
d4rk4ng31

Câu trả lời:


6

Bạn có thể sử dụng 2 vòng:

  • Lấy từng n-tuple
  • lặp đi lặp lại trên hoán vị của n-tuple đó
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()));
}

Bản giới thiệu


Điều gì sẽ là phức tạp thời gian của mã này? Nó sẽ là O (địa điểm được yêu cầu * n) cho trường hợp trung bình và O (n ^ 2) cho trường hợp xấu nhất? Tôi cũng đoán O (n) cho trường hợp tốt nhất, tức là một nơi
d4rk4ng31

2
@ d4rk4ng31: Chúng tôi thực sự gặp phải mỗi hoán vị chỉ một lần. độ phức tạp của std::next_permutation"không rõ ràng" vì nó tính trao đổi (Tuyến tính). Khai thác vectơ phụ có thể được cải thiện, nhưng tôi không nghĩ nó thay đổi độ phức tạp. Ngoài ra số lượng hoán vị phụ thuộc vào kích thước vectơ, vì vậy tham số 2 không độc lập.
Jarod42

Có nên như std::vector<T>& vvậy không?
LF

@LF: Đó là mục đích. Tôi xem xét rằng tôi không phải thay đổi giá trị của người gọi ( vhiện tại tôi sắp xếp ). Tôi có thể chuyển qua tham chiếu const và thay vào đó tạo một bản sao được sắp xếp trong cơ thể.
Jarod42

@ Jarod42 Oh xin lỗi, tôi đã đọc sai mã. Vâng, vượt qua giá trị là điều đúng đắn để làm ở đây.
LF

4

Nếu hiệu quả không phải là mối quan tâm chính, chúng ta có thể lặp đi lặp lại trên tất cả các hoán vị và bỏ qua những thứ khác nhau trên một hậu tố chỉ chọn mỗi (N - k)!thứ. Ví dụ: cho N = 4, k = 2, chúng tôi có hoán vị:

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ơi tôi chèn một khoảng (N-k)! = 2! = 2trắng cho rõ ràng và đánh dấu mỗi hoán vị -nd với <.

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 << ' ';
    });
}

Đầu ra:

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

3

Đây là một thuật toán hiệu quả không sử dụng std::next_permutationtrực tiếp, nhưng sử dụng ngựa làm việc của hàm đó. Đó là, std::swapstd::reverse. Thêm vào đó, nó theo thứ tự từ điển .

#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());
    }
}

Và gọi nó là chúng ta có:

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;
}

Đây là đầu ra:

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

Đây là mã runnable từ ideone


2
Bạn thậm chí có thể bắt chước nhiều hơn với chữ kýbool nextPartialPermutation(It begin, It mid, It end)
Jarod42


@ Jarod42, đó là một giải pháp thực sự tốt đẹp. Bạn nên thêm nó như một câu trả lời ...
Joseph Wood

Ý tưởng ban đầu của tôi là cải thiện câu trả lời của bạn, nhưng ok, thêm vào.
Jarod42

3

Trả lời Joseph Wood với giao diện iterator, bạn có thể có một phương thức tương tự như 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;
    }
}

Bản giới thiệu


1

Đây là giải pháp của tôi sau một số suy nghĩ

#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;
}

Hãy bình luận suy nghĩ của bạn


2
Không tương đương với tôi, nó tương đương với câu trả lời của Evg, nhưng Evg bỏ qua các bản sao hiệu quả hơn). permutetrong thực tế chỉ có thể được set.insert(vec);loại bỏ một yếu tố lớn.
Jarod42

Sự phức tạp thời gian bây giờ là gì?
d4rk4ng31

1
Tôi sẽ nói O(nb_total_perm * log(nb_res))( nb_total_permphần lớn factorial(job_list.size())nb_reskích thước của kết quả permutations.size():), vì vậy vẫn còn quá lớn. (nhưng bây giờ bạn xử lý trùng lặp đầu vào trái với Evg)
Jarod42
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.