Como criar uma permutação em c ++ usando STL para número de locais menor que o comprimento total

15

Eu tenho um c++ vectorcom std::pair<unsigned long, unsigned long>objetos. Estou tentando gerar permutações dos objetos do vetor usando std::next_permutation(). No entanto, eu quero que as permutações sejam de um determinado tamanho, você sabe, semelhante à permutationsfunção em python em que o tamanho da permutação retornada esperada é especificado.

Basicamente, o c++equivalente a

import itertools

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

Demonstração do 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) 
d4rk4ng31
fonte
Obrigado @ Jarod42 por adicionar essa demo python :)
d4rk4ng31
Tive que fazer isso do meu lado, pois não sei o resultado do python, mas tinha certeza de que sei como fazê-lo em C ++.
Jarod42 23/04
Como uma observação lateral, como você deseja lidar com entradas duplicadas como (1, 1)? permutações python fornecem duplicado [(1, 1), (1, 1)], enquanto std::next_permutationevitam duplicados (apenas {1, 1}).
Jarod42 23/04
Oh não. Não há duplicatas
d4rk4ng31

Respostas:

6

Você pode usar 2 loops:

  • Tome cada n-tupla
  • iteram sobre permutações dessa n-tupla
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()));
}

Demo

Jarod42
fonte
Qual será a complexidade temporal deste código? Será O (lugares_requisitos * n) para o caso médio e O (n ^ 2) para o pior caso? Também estou adivinhando O (n) para o melhor caso, ou seja, um lugar
d4rk4ng31
2
@ d4rk4ng31: De fato, encontramos cada permutação apenas uma vez. A complexidade de std::next_permutationé "pouco clara", pois conta a troca (Linear). A extração do sub-vetor pode ser melhorada, mas não acho que isso mude a complexidade. Além disso, o número de permutações depende do tamanho do vetor; portanto, o parâmetro 2 não é independente.
Jarod42 23/04
Isso não deveria ser std::vector<T>& v?
LF
@LF: É de propósito. Considero que não preciso alterar o valor do chamador ( vatualmente classifico ). Eu poderia passar por referência const e criar uma cópia classificada no corpo.
Jarod42 25/04
@ Jarod42 Oh, desculpe, eu li completamente o código. Sim, passar por valor é a coisa certa a fazer aqui.
LF
4

Se a eficiência não for a principal preocupação, podemos iterar todas as permutações e pular aquelas que diferem em um sufixo, selecionando apenas cada (N - k)!uma delas. Por exemplo, para N = 4, k = 2, temos permutações:

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

onde inseri um espaço para maior clareza e marquei cada (N-k)! = 2! = 2permutação com <.

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

Resultado:

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

Aqui está um algoritmo eficiente que não usa std::next_permutationdiretamente, mas utiliza os cavalos de trabalho dessa função. Isto é, std::swape std::reverse. Como um plus, está em ordem lexicográfica .

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

E chamando, temos:

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

Aqui está a saída:

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

Aqui está o código executável do ideone

Joseph Wood
fonte
2
Você pode até imitar ainda mais com a assinaturabool nextPartialPermutation(It begin, It mid, It end)
Jarod42 23/04
2
Demo .
Jarod42 23/04
@ Jarod42, essa é uma solução muito boa. Você deve adicioná-lo como resposta ...
Joseph Wood
Minha idéia inicial foi melhorar sua resposta, mas tudo bem, acrescentou.
Jarod42 24/04
3

Para responder a Joseph Wood com a interface do iterador, você pode ter um método semelhante a 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;
    }
}

Demo

Jarod42
fonte
1

Esta é a minha solução depois de pensar um pouco

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

Por favor, comente seus pensamentos

d4rk4ng31
fonte
2
Não é equivalente à minha, é mais equivalente à resposta do Evg (mas o Evg ignora duplicatas com mais eficiência). permutede fato, apenas set.insert(vec);remover um grande fator.
Jarod42 23/04
Qual é a complexidade de tempo agora?
d4rk4ng31 23/04
11
Eu diria O(nb_total_perm * log(nb_res))( nb_total_permque é principalmente factorial(job_list.size())e nb_restamanho do resultado permutations.size():), portanto, ainda é muito grande. (mas agora você lida com entradas duplicadas contrárias ao Evg)
Jarod42