Toplam uzunluktan daha az yer sayısı için STL kullanarak c ++ 'da permütasyon nasıl oluşturulur


15

Bir var c++ vectorolan std::pair<unsigned long, unsigned long>nesneler. Kullanarak vektör nesnelerinin permütasyonları üretmeye çalışıyorum std::next_permutation(). Ancak, permütasyonların permutations, beklenen döndürülmüş permütasyonun boyutunun belirtildiği python işlevine benzer şekilde belirli bir boyutta olmasını istiyorum .

Temel olarak, c++eşdeğer

import itertools

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

Python Demosu

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

Bu python demosunu eklediğiniz için teşekkürler @ Jarod42 :)
d4rk4ng31

Python sonucunu bilmediğim gibi benim tarafımda yapmak zorundaydım, ama C ++ ile nasıl yapacağımı bildiğimden oldukça emindim.
Jarod42

Yan not olarak, yinelenen girişleri nasıl ele almak istersiniz (1, 1)? python permütasyonları çoğaltılmış [(1, 1), (1, 1)]sağlarken , yinelemelerden std::next_permutationkaçının (sadece {1, 1}).
Jarod42

Oh hayır. Kopya yok
d4rk4ng31

Yanıtlar:


6

2 döngü kullanabilirsiniz:

  • Her n-demetini alın
  • o n-demetinin permütasyonları üzerinde yineleme
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()));
}

gösteri


Bu kodun zaman karmaşıklığı ne olacak? Ortalama vaka için O (basamak_gerekli * n) ve en kötü durum için O (n ^ 2) olacak mı? Ben de O (n) en iyi durumda, yani bir yer için tahmin ediyorum
d4rk4ng31

2
@ d4rk4ng31: Gerçekten her bir permütasyonla sadece bir kez karşılaşıyoruz. std::next_permutationtakas sayılırken karmaşıklığı "belirsizdir" (Doğrusal). Alt vektörün çıkarılması geliştirilebilir, ancak karmaşıklığını değiştirdiğini sanmıyorum. Ek olarak permütasyon sayısı vektör boyutuna bağlıdır, bu nedenle 2 parametresi bağımsız değildir.
Jarod42

Olmamalı mı std::vector<T>& v?
LF

@LF: Amaçlıdır. Arayanın değerini değiştirmek zorunda olmadığımı düşünüyorum ( vşu anda sıralıyorum). Const referansı ile geçebilir ve bunun yerine vücutta sıralı bir kopya oluşturabilirim.
Jarod42

@ Jarod42 Üzgünüm, kodu tamamen yanlış okudum. Evet, değerden geçmek burada yapılacak doğru şeydir.
LF

4

Verimlilik birincil sorun değilse, tüm permütasyonları tekrarlayabilir ve yalnızca her (N - k)!birini seçen bir sonekte farklılık gösterenleri atlayabiliriz . Örneğin, N = 4, k = 2permütasyonlarımız var:

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

burada netlik için bir boşluk (N-k)! = 2! = 2ekledim ve her-ve permütasyon ile işaretledim <.

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

Çıktı:

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

3

İşte std::next_permutationdoğrudan kullanmayan , ancak bu işlevin çalışma atlarını kullanan etkili bir algoritma . Bu std::swapve std::reverse. Bir artı olarak, sözlükbilimsel düzende .

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

Ve onu çağırıyoruz:

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

İşte çıktı:

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

İşte ideone'den çalıştırılabilir kod


2
bool nextPartialPermutation(It begin, It mid, It end)
İmzayla


@ Jarod42, bu gerçekten güzel bir çözüm. Cevap olarak eklemelisin ...
Joseph Wood

İlk fikrim cevabınızı geliştirmekti, ama tamam ekledi.
Jarod42

3

Joseph Wood yanıtını yineleyici arayüzü ile çevirerek, aşağıdakine benzer bir yönteminiz olabilir 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;
    }
}

gösteri


1

Bu benim düşüncemden sonra benim çözümüm

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

Lütfen düşüncelerinizi yorumlayın


2
Benimkine eşdeğer değil, Evg'in cevabına daha eşdeğerdir (ancak Evg, kopyaları daha verimli bir şekilde atlar). permuteaslında sadece set.insert(vec);büyük bir faktörü ortadan kaldırmak olabilir .
Jarod42

Şimdi zaman karmaşıklığı nedir?
d4rk4ng31

1
Ben söyleyebilirim O(nb_total_perm * log(nb_res))( nb_total_permçoğunlukla factorial(job_list.size())ve nb_ressonuç büyüklüğü:) permutations.size(), Yani hala çok büyük. (ama şimdi Evg'in aksine girişleri kopyalıyorsunuz)
Jarod42
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.