std :: next_permutation Uygulama Açıklaması


110

Nasıl uygulandığını merak ediyordum std:next_permutation, bu yüzden gnu libstdc++ 4.7sürümü çıkardım ve aşağıdaki demoyu üretmek için tanımlayıcıları ve biçimlendirmeyi temizledim ...

#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

template<typename It>
bool next_permutation(It begin, It end)
{
        if (begin == end)
                return false;

        It i = begin;
        ++i;
        if (i == end)
                return false;

        i = end;
        --i;

        while (true)
        {
                It j = i;
                --i;

                if (*i < *j)
                {
                        It k = end;

                        while (!(*i < *--k))
                                /* pass */;

                        iter_swap(i, k);
                        reverse(j, end);
                        return true;
                }

                if (i == begin)
                {
                        reverse(begin, end);
                        return false;
                }
        }
}

int main()
{
        vector<int> v = { 1, 2, 3, 4 };

        do
        {
                for (int i = 0; i < 4; i++)
                {
                        cout << v[i] << " ";
                }
                cout << endl;
        }
        while (::next_permutation(v.begin(), v.end()));
}

Çıktı beklendiği gibi: http://ideone.com/4nZdx

Sorularım: Nasıl çalışıyor? Ne anlamı olan i, jve k? Uygulamanın farklı bölümlerinde ne gibi bir değere sahipler? Doğruluğunun bir kanıtı nedir?

Açıkça ana döngüye girmeden önce, önemsiz 0 veya 1 eleman listesi durumlarını kontrol eder. Ana döngünün girişinde i, son elemana (bir son uca değil) işaret etmektedir ve liste en az 2 eleman uzunluğundadır.

Ana döngünün gövdesinde neler oluyor?


Hey, bu kod parçasını nasıl çıkardın? #İnclude <algorithm> i işaretlediğimde, kod tamamen farklıydı ve daha fazla işlevden oluşuyordu
Manjunath

Yanıtlar:


172

Bazı permütasyonlara bakalım:

1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
2 1 3 4
...

Bir permütasyondan diğerine nasıl geçebiliriz? Öncelikle olaylara biraz farklı bakalım. Elemanları rakamlar ve permütasyonları sayı olarak görebiliriz . Problemi bu şekilde inceleyerek permütasyonları / sayıları "artan" sırada sıralamak istiyoruz .

Numaraları sıralarken "onları en küçük miktarda artırmak" isteriz. Örneğin sayarken 1, 2, 3, 10, ... saymıyoruz çünkü aralarında hala 4, 5, ... var ve 10 3'ten büyük olmasına rağmen, eksik sayılar var 3'ü daha küçük bir miktarda artırmak. Yukarıdaki örnekte 1, permütasyonu daha küçük bir miktarda "artıran" son 3 "basamağın" birçok yeniden sıralaması olduğundan, bunun uzun bir süre ilk sayı olarak kaldığını görüyoruz .

Peki nihayet ne zaman "kullanacağız" 1? Son 3 basamağın artık permütasyonu olmadığında.
Ve son 3 basamağın daha fazla permütasyonu ne zaman olmaz? Son 3 rakam azalan sırada olduğunda.

Aha! Bu, algoritmayı anlamanın anahtarıdır. Yalnızca sağdaki her şey azalan sırada olduğunda bir "rakamın" konumunu değiştiririz çünkü eğer bu azalan sırada değilse, o zaman gidecek daha fazla permütasyon vardır (yani permütasyonu daha küçük bir miktarda "artırabiliriz") .

Şimdi koda geri dönelim:

while (true)
{
    It j = i;
    --i;

    if (*i < *j)
    { // ...
    }

    if (i == begin)
    { // ...
    }
}

Döngüdeki ilk 2 satırdan itibaren j, bir elementtir ve iondan önceki elementtir.
Ardından, öğeler artan sıradaysa, ( if (*i < *j)) bir şeyler yapın.
Aksi takdirde, her şey azalan sıradaysa, ( if (i == begin)) bu son permütasyondur.
Aksi takdirde devam ederiz ve j ve i'nin esasen azaldığını görürüz.

Şimdi if (i == begin)kısmı anlıyoruz, bu yüzden anlamamız gereken tek şey o if (*i < *j)kısım.

Ayrıca şunu not edin: "Öyleyse, eğer elemanlar artan sıradaysa ...", bu bizim sadece bir rakama bir şeyler yapmamız gerektiğine dair önceki gözlemimizi destekler "sağdaki her şey azalan sıradayken". Artan düzen ififadesi, esasen "sağdaki her şeyin azalan sırada olduğu" en soldaki yeri buluyor.

Bazı örneklere tekrar bakalım:

...
1 4 3 2
2 1 3 4
...
2 4 3 1
3 1 2 4
...

Bir basamağın sağındaki her şey azalan sırada olduğunda, bir sonraki en büyük basamağı bulup önüne koyduğumuzu ve sonra kalan basamakları artan sırayla koyduğumuzu görüyoruz .

Koda bakalım:

It k = end;

while (!(*i < *--k))
    /* pass */;

iter_swap(i, k);
reverse(j, end);
return true;

Sağdaki şeyler azalan sırada olduğundan, "sonraki en büyük rakamı" bulmak için, kodun ilk 3 satırında gördüğümüz sondan itibaren yinelemeliyiz.

Daha sonra, "sonraki en büyük rakamı" ön tarafla iter_swap()ifadeyle değiştiririz ve sonra bu rakamın bir sonraki en büyük rakam olduğunu bildiğimiz için, sağdaki rakamların hala azalan sırada olduğunu biliyoruz, bu yüzden onu artan sırada koymak, sadece yapmalıyız reverse().


12
Şaşırtıcı açıklama

2
Açıklama için teşekkürler! Bu algoritmaya, sözlük sırasına göre Üretim adı verilir . İçinde böyle bir algoritma var Combinatorics, ama bu en klasik olanı.
chain ro

1
Böyle bir algoritmanın karmaşıklığı nedir?
user72708

leetcode iyi bir açıklamaya sahip, leetcode.com/problems/next-permutation/solution
bicepjai

40

Gcc uygulaması, sözlüksel sırayla permütasyonlar üretir. Wikipedia bunu şu şekilde açıklıyor:

Aşağıdaki algoritma, belirli bir permütasyondan sonra bir sonraki permütasyonu sözlükbilimsel olarak üretir. Verilen permütasyonu yerinde değiştirir.

  1. A [k] <a [k + 1] olacak şekilde en büyük k endeksini bulun. Böyle bir indeks yoksa permütasyon son permütasyondur.
  2. A [k] <a [l] olacak şekilde en büyük l indisini bulun. K + 1 böyle bir indeks olduğundan, l iyi tanımlanmıştır ve k <l'yi sağlar.
  3. A [k] ile [l] arasında yer değiştirin.
  4. Sırayı bir [k + 1] 'den son eleman a [n]' ye kadar ters çevirin.

AFAICT, tüm uygulamalar aynı sırayı üretir.
MSalters

12

Knuth, bu algoritma ve The Art of Computer Programming'in 7.2.1.2 ve 7.2.1.3 bölümlerindeki genellemeleri hakkında derinlemesine incelemektedir . Ona "Algoritma L" diyor - görünüşe göre 13. yüzyıla kadar uzanıyor.


1
Kitabın adını söyler misiniz lütfen?
Grobber

3
TAOCP = Bilgisayar Programlama Sanatı

9

İşte diğer standart kitaplık algoritmalarını kullanan eksiksiz bir uygulama:

template <typename I, typename C>
    // requires BidirectionalIterator<I> && Compare<C>
bool my_next_permutation(I begin, I end, C comp) {
    auto rbegin = std::make_reverse_iterator(end);
    auto rend = std::make_reverse_iterator(begin);
    auto rsorted_end = std::is_sorted_until(rbegin, rend, comp);
    bool has_more_permutations = rsorted_end != rend;
    if (has_more_permutations) {
        auto next_permutation_rend = std::upper_bound(
            rbegin, rsorted_end, *rsorted_end, comp);
        std::iter_swap(rsorted_end, next_permutation_rend);
    }
    std::reverse(rbegin, rsorted_end);
    return has_more_permutations;
}

gösteri


1
Bu, iyi değişken adlarının ve endişelerin ayrılığının öneminin altını çizmektedir. is_final_permutationdaha bilgilendirici begin == end - 1. Çağırmak is_sorted_until/ upper_boundpermütasyon mantığını bu işlemlerden ayırır ve bunu çok daha anlaşılır hale getirir. Ek olarak, üst_bound bir ikili aramadır, ancak while (!(*i < *--k));doğrusaldır, bu nedenle bu daha performanslıdır.
Jonathan Gawrych

1

Bir öz açıklayıcı olası uygulaması mevcuttur cppreference kullanarak <algorithm>.

template <class Iterator>
bool next_permutation(Iterator first, Iterator last) {
    if (first == last) return false;
    Iterator i = last;
    if (first == --i) return false;
    while (1) {
        Iterator i1 = i, i2;
        if (*--i < *i1) {
            i2 = last;
            while (!(*i < *--i2));
            std::iter_swap(i, i2);
            std::reverse(i1, last);
            return true;
        }
        if (i == first) {
            std::reverse(first, last);
            return false;
        }
    }
}

İçeriği sözlükbilimsel olarak bir sonraki permütasyona (yerinde) değiştirin ve varsa doğru olarak döndürün, aksi takdirde sıralayın ve yoksa yanlış döndür.

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.