Comment créer une permutation en c ++ en utilisant STL pour un nombre de places inférieur à la longueur totale

15

J'ai un c++ vectoravec des std::pair<unsigned long, unsigned long>objets. J'essaie de générer des permutations des objets du vecteur en utilisantstd::next_permutation() . Cependant, je veux que les permutations soient d'une taille donnée, vous savez, similaire à la permutationsfonction en python où la taille de la permutation retournée attendue est spécifiée.

Fondamentalement, l' c++équivalent de

import itertools

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

Démo 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
la source
Merci @ Jarod42 pour avoir ajouté cette démo python :)
d4rk4ng31
Je devais le faire de mon côté, car je ne connais pas le résultat python, mais j'étais presque sûr de savoir comment le faire en C ++.
Jarod42
En remarque, comment voulez-vous gérer les entrées en double (1, 1)? permutations python fournit dupliqué [(1, 1), (1, 1)], tandis questd::next_permutation évitez les doublons (uniquement {1, 1}).
Jarod42
Euh non. Pas de doublons
d4rk4ng31

Réponses:

6

Vous pouvez utiliser 2 boucles:

  • Prenez chaque n-tuple
  • itérer sur les permutations de ce 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()));
}

Démo

Jarod42
la source
Quelle sera la complexité temporelle de ce code? Sera-ce O (places_required * n) pour le cas moyen et O (n ^ 2) pour le pire des cas? Je devine également O (n) pour le meilleur cas, c'est-à-dire un seul endroit
d4rk4ng31
2
@ d4rk4ng31: Nous ne rencontrons en effet chaque permutation qu'une seule fois. La complexité de std::next_permutation"n'est pas claire" car elle compte le swap (linéaire). L'extraction du sous-vecteur peut être améliorée, mais je ne pense pas que cela change la complexité. De plus, le nombre de permutations dépend de la taille du vecteur, donc les 2 paramètres ne sont pas indépendants.
Jarod42
N'est-ce pas std::vector<T>& v?
LF
@LF: C'est exprès. Je considère que je n'ai pas à changer la valeur de l'appelant (je trie vactuellement). Je pourrais passer par référence const et créer une copie triée dans le corps à la place.
Jarod42
@ Jarod42 Oh désolé, j'ai complètement mal lu le code. Oui, passer par la valeur est la bonne chose à faire ici.
LF
4

Si l'efficacité n'est pas la principale préoccupation, nous pouvons parcourir toutes les permutations et ignorer celles qui diffèrent sur un suffixe ne sélectionnant que chaque (N - k)!-ième. Par exemple, pour N = 4, k = 2, nous avons des permutations:

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

où j'ai inséré un espace pour plus de clarté et marqué chaque (N-k)! = 2! = 2permutation -nd avec <.

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

Production:

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

Voici un algorithme efficace qui n'utilise pas std::next_permutationdirectement, mais utilise les chevaux de travail de cette fonction. C'est, std::swapet std::reverse. En plus, c'est dans l' ordre lexicographique .

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

Et en l'appelant, nous avons:

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

Voici la sortie:

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

Voici le code exécutable de exécutable ideone

Joseph Wood
la source
2
Vous pourriez même imiter encore plus avec la signaturebool nextPartialPermutation(It begin, It mid, It end)
Jarod42
2
Démo .
Jarod42
@ Jarod42, c'est une très bonne solution. Vous devriez l'ajouter comme réponse ...
Joseph Wood
Mon idée initiale était d'améliorer votre réponse, mais d'accord, a ajouté.
Jarod42
3

En tournant Joseph Wood avec l'interface de l'itérateur, vous pourriez avoir une méthode similaire à 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;
    }
}

Démo

Jarod42
la source
1

Ceci est ma solution après réflexion

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

Veuillez commenter vos pensées

d4rk4ng31
la source
2
Pas équivalent au mien, il est plus équivalent à la réponse d'Evg (mais Evg ignore les doublons plus efficacement). permutene pouvait en fait que set.insert(vec);supprimer un grand facteur.
Jarod42
Quelle est la complexité du temps maintenant?
d4rk4ng31
1
Je dirais O(nb_total_perm * log(nb_res))( nb_total_permce qui est surtout factorial(job_list.size())et la nb_restaille du résultat permutations.size():), donc toujours trop gros. (mais maintenant vous gérez les doublons contrairement à Evg)
Jarod42