Як створити перестановку в c ++ за допомогою STL для кількості місць, менших від загальної довжини


15

У мене є c++ vectorз std::pair<unsigned long, unsigned long>об'єктами. Я намагаюся генерувати перестановки об'єктів вектора за допомогою std::next_permutation(). Однак я хочу, щоб перестановки були заданого розміру, як ви знаєте, аналогічно permutationsфункції в python, де вказано розмір очікуваної повернутої перестановки.

В основному, 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 demo :)
d4rk4ng31

Довелося це зробити на моєму боці, оскільки я не знаю результату python, але був впевнений, що знаю, як це зробити в C ++.
Jarod42

Як бічна примітка, як ви хочете обробляти дублікати входів як (1, 1)? Перестановки python забезпечують дублювання [(1, 1), (1, 1)], тоді як std::next_permutationуникайте дублікатів (лише {1, 1}).
Jarod42

Ага .. ні. Без дублікатів
d4rk4ng31

Відповіді:


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 (місця_потребує * n) для середнього випадку та O (n ^ 2) для гіршого випадку? Я також здогадуюсь про кращий випадок (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::swapі std::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, це дійсно приємне рішення. Ви повинні додати це як відповідь ...
Джозеф Вуд

Моя початкова ідея полягала в тому, щоб поліпшити вашу відповідь, але добре, додав.
Jarod42

3

Якщо увімкнути відповідь Джозефа Вуда за допомогою інтерфейсу ітератора, у вас може бути метод, подібний до 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
Не еквівалентний моєму, він більш еквівалентний відповіді Евга (але Евг пропускає дублікати ефективніше). 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,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.