Come creare una permutazione in c ++ usando STL per un numero di posizioni inferiore alla lunghezza totale


15

Ho un c++ vectorcon std::pair<unsigned long, unsigned long>gli oggetti. Sto cercando di generare permutazioni degli oggetti del vettore usando std::next_permutation(). Tuttavia, voglio che le permutazioni abbiano una determinata dimensione, sai, simile alla permutationsfunzione in Python in cui è specificata la dimensione della permutazione restituita prevista.

Fondamentalmente, l' c++equivalente di

import itertools

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

Demo 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) 

Grazie @ Jarod42 per aver aggiunto la demo di Python :)
d4rk4ng31,

Ho dovuto farlo dalla mia parte, poiché non conosco il risultato di Python, ma ero abbastanza sicuro di sapere come farlo in C ++.
Jarod42,

Come nota a margine, come si desidera gestire input duplicati come (1, 1)? permutazioni python fornisce duplicati [(1, 1), (1, 1)], mentre std::next_permutationevitare duplicati (solo {1, 1}).
Jarod42,

Uh .. no. Nessun duplicato
d4rk4ng31

Risposte:


6

È possibile utilizzare 2 loop:

  • Prendi ogni n-tupla
  • iterare su permutazioni di quella 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()));
}

dimostrazione


Quale sarà la complessità temporale di questo codice? Sarà O (places_required * n) per il caso medio e O (n ^ 2) per il caso peggiore? Sto anche indovinando O (n) per il miglior caso, cioè un posto
d4rk4ng31

2
@ d4rk4ng31: In effetti incontriamo ogni permutazione una sola volta. la complessità di std::next_permutationè "poco chiara" in quanto conta swap (lineare). L'estrazione del vettore secondario può essere migliorata, ma non credo che cambi complessità. Inoltre il numero di permutazione dipende dalla dimensione del vettore, quindi i 2 parametri non sono indipendenti.
Jarod42,

Non dovrebbe essere std::vector<T>& v?
LF

@LF: è apposta. Considero che non devo modificare il valore del chiamante (l'ordinamento è vattualmente). Potrei passare per riferimento const e creare invece una copia ordinata nel corpo.
Jarod42,

@ Jarod42 Oh scusa, ho letto male il codice. Sì, passare per valore è la cosa giusta da fare qui.
LF

4

Se l'efficienza non è la preoccupazione principale, possiamo iterare su tutte le permutazioni e saltare quelle che differiscono su un suffisso selezionando solo ciascuna di (N - k)!esse. Ad esempio, per N = 4, k = 2, abbiamo permutazioni:

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

dove ho inserito uno spazio per la chiarezza e contrassegnato ciascuna (N-k)! = 2! = 2e la permutazione con <.

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

Produzione:

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

3

Ecco un algoritmo efficiente che non utilizza std::next_permutationdirettamente, ma utilizza i cavalli da lavoro di quella funzione. Cioè, std::swape std::reverse. Inoltre, è in ordine lessicografico .

#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 chiamandolo abbiamo:

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

Ecco l'output:

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

Ecco il codice eseguibile da ideone


2
Potresti anche imitare ancora di più con la firmabool nextPartialPermutation(It begin, It mid, It end)
Jarod42


@ Jarod42, questa è davvero una bella soluzione. Dovresti aggiungerlo come risposta ...
Joseph Wood,

La mia idea iniziale era di migliorare la tua risposta, ma ok, ha aggiunto.
Jarod42,

3

Girando la risposta di Joseph Wood con l'interfaccia iteratrice, potresti avere un metodo simile 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;
    }
}

dimostrazione


1

Questa è la mia soluzione dopo qualche riflessione

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

Per favore, commenta i tuoi pensieri


2
Non equivalente al mio, è più equivalente alla risposta di Evg (ma Evg salta i duplicati in modo più efficiente). permutepotrebbe infatti solo set.insert(vec);rimuovere un fattore importante.
Jarod42,

Qual è la complessità temporale ora?
d4rk4ng31,

1
Direi O(nb_total_perm * log(nb_res))( nb_total_permche è principalmente factorial(job_list.size())e nb_resdimensione del risultato permutations.size():), quindi ancora troppo grande. (ma ora gestisci i duplicati immessi in contrasto con Evg)
Jarod42
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.