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) 
d4rk4ng31
quelle
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

Jarod42
quelle
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
Evg
quelle
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

Joseph Wood
quelle
2
Mit der Unterschrift könnten Sie sogar noch mehr nachahmenbool nextPartialPermutation(It begin, It mid, It end)
Jarod42
2
Demo .
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

Jarod42
quelle
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

d4rk4ng31
quelle
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