So erstellen Sie eine Permutation in c ++ mit STL für eine Anzahl von Stellen, die unter der Gesamtlänge liegen


15

Ich habe eine c++ vectormit std::pair<unsigned long, unsigned long>Gegenständen. Ich versuche, Permutationen der Objekte des Vektors mit zu erzeugen std::next_permutation(). Ich möchte jedoch, dass die Permutationen eine bestimmte Größe haben, ähnlich der permutationsFunktion in Python, bei der die Größe der erwarteten zurückgegebenen Permutation angegeben wird.

Grundsätzlich c++entspricht das Äquivalent von

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) 

Danke @ Jarod42 für das Hinzufügen dieser Python-Demo :)
d4rk4ng31

Musste es auf meiner Seite tun, da ich das Python-Ergebnis nicht kenne, aber ich war mir ziemlich sicher, dass ich weiß, wie man es in C ++ macht.
Jarod42

Wie möchten Sie als Randnotiz mit doppelten Eingaben umgehen (1, 1)? Python-Permutationen bieten Duplikate [(1, 1), (1, 1)], während std::next_permutationDuplikate (nur {1, 1}) vermieden werden .
Jarod42

Oh nein. Keine Duplikate
d4rk4ng31

Antworten:


6

Sie können 2 Schleifen verwenden:

  • Nimm jedes n-Tupel
  • iteriere über Permutationen dieses n-Tupels
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


Was wird die zeitliche Komplexität dieses Codes sein? Wird es O (Orte_erforderlich * n) für den Durchschnittsfall und O (n ^ 2) für den schlimmsten Fall sein? Ich
vermute

2
@ d4rk4ng31: Wir begegnen tatsächlich jeder Permutation nur einmal. Die Komplexität von std::next_permutationist "unklar", da es sich um Swap (linear) handelt. Die Extraktion des Subvektors kann verbessert werden, aber ich denke nicht, dass dies die Komplexität ändert. Außerdem hängt die Anzahl der Permutationen von der Vektorgröße ab, sodass die Parameter 2 nicht unabhängig sind.
Jarod42

Sollte das nicht sein std::vector<T>& v?
LF

@LF: Es ist absichtlich. Ich denke, dass ich den Wert des Anrufers nicht ändern muss (ich sortiere vderzeit). Ich könnte an einer const-Referenz vorbeikommen und stattdessen eine sortierte Kopie im Text erstellen.
Jarod42

@ Jarod42 Oh sorry, ich habe den Code komplett falsch verstanden. Ja, Wertübergabe ist hier das Richtige.
LF

4

Wenn Effizienz nicht das Hauptanliegen ist, können wir alle Permutationen durchlaufen und diejenigen überspringen, die sich in einem Suffix unterscheiden, indem wir nur jedes (N - k)!-te auswählen . Zum Beispiel N = 4, k = 2haben wir Permutationen:

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

wo ich ein Leerzeichen für Klarheit eingefügt und jede (N-k)! = 2! = 2-nd Permutation mit markiert habe <.

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

Ausgabe:

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

3

Hier ist ein effizienter Algorithmus, der nicht std::next_permutationdirekt verwendet wird, sondern die Arbeitspferde dieser Funktion verwendet. Das heißt std::swapund std::reverse. Als Plus ist es in lexikographischer Reihenfolge .

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

Und wenn wir es nennen, haben wir:

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

Hier ist die Ausgabe:

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

Hier ist ausführbarer Code von ideone


2
Mit der Unterschrift könnten Sie sogar noch mehr nachahmenbool nextPartialPermutation(It begin, It mid, It end)
Jarod42


@ Jarod42, das ist eine wirklich schöne Lösung. Sie sollten es als Antwort hinzufügen ...
Joseph Wood

Meine ursprüngliche Idee war es, Ihre Antwort zu verbessern, aber ok, fügte hinzu.
Jarod42

3

Wenn Sie die Antwort von Joseph Wood mit der Iterator-Oberfläche drehen, haben Sie möglicherweise eine ähnliche Methode wie 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


1

Dies ist meine Lösung nach einigem Nachdenken

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

Bitte kommentieren Sie Ihre Gedanken


2
Nicht gleichbedeutend mit meiner, sondern eher mit Evgs Antwort (aber Evg überspringt Duplikate effizienter). permutekönnte in der Tat nur set.insert(vec);einen großen Faktor entfernen.
Jarod42

Was ist jetzt die zeitliche Komplexität?
d4rk4ng31

1
Ich würde sagen O(nb_total_perm * log(nb_res))( nb_total_permwas meistens factorial(job_list.size())und nb_resGröße des Ergebnisses ist :) permutations.size(), also immer noch zu groß. (aber jetzt behandeln Sie doppelte Eingaben im Gegensatz zu Evg)
Jarod42
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.