Как создать перестановку в 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 Demo

(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) 
d4rk4ng31
источник
Спасибо @ Jarod42 за добавление этой демонстрации Python :)
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()));
}

демонстрация

Jarod42
источник
Какова будет временная сложность этого кода? Это будет O (place_required * n) для среднего случая и O (n ^ 2) для худшего случая? Я также угадываю O (n) для лучшего случая, то есть одно место
d4rk4ng31
2
@ d4rk4ng31: Мы действительно сталкиваемся с каждой перестановкой только один раз. Сложность std::next_permutation«неясна», так как считается свопом (линейным). Извлечение субвектора можно улучшить, но я не думаю, что это изменит сложность. Кроме того, число перестановок зависит от размера вектора, поэтому 2 параметра не являются независимыми.
Jarod42
Не должно ли это быть std::vector<T>& v?
LF
@LF: это специально. Я считаю, что мне не нужно менять значение вызывающей стороны (я сортирую в vнастоящее время). Я мог бы пройти по константной ссылке и вместо этого создать отсортированную копию в теле.
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
Evg
источник
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
2
Demo .
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;
    }
}

демонстрация

Jarod42
источник
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;
}

Пожалуйста, прокомментируйте свои мысли

d4rk4ng31
источник
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