Modern C ++ 'da klasik sıralama algoritmaları nasıl uygulanır?


331

std::sortAlgoritma (ve Kuzenleri std::partial_sortve std::nth_elementC ++ Standart Kütüphaneden) Bir çok uygulamada olduğu daha basit sıralama algoritma bir karmaşık ve hibrid birleşmesi gibi sıralama seçimi, sıralama, hızlı sıralama ekleme, sıralama birleştirme ya da yığın türü olarak.

Burada ve https://codereview.stackexchange.com/ gibi kardeş sitelerde hatalar, karmaşıklık ve bu klasik sıralama algoritmalarının uygulamalarının diğer yönleri ile ilgili birçok soru var . Sunulan uygulamaların çoğu ham döngülerden oluşur, indeks manipülasyonu ve beton türlerini kullanır ve genellikle doğruluk ve verimlilik açısından analiz etmek önemsizdir.

Soru : Yukarıda belirtilen klasik sıralama algoritmaları modern C ++ kullanılarak nasıl uygulanabilir?

  • ham döngü yok , ancak Standart Kütüphanenin algoritmik yapı taşlarını<algorithm>
  • yineleyici arabirimi ve dizin işleme ve somut türler yerine şablonların kullanımı
  • C ++ 14 stili , tam Standart Kitaplık autove şablon takma adları, saydam karşılaştırıcılar ve polimorfik lambdalar gibi sözdizimsel gürültü azaltıcıları içerir .

Notlar :

  • sıralama algoritmaları uygulamalarına ilişkin daha fazla referans için Wikipedia , Rosetta Kodu veya http://www.sorting-algorithms.com/ adresine bakın.
  • uygun Sean Ebeveyn sözleşmeler (slayt 39), ham bir döngü olup for-loop uzun bir operatör ile iki fonksiyon bileşimine göre. Yani f(g(x));ya f(x); g(x);ya f(x) + g(x);çiğ döngüler değildir ve ne de döngüdür selection_sortve insertion_sortaşağıda.
  • Şu anda C ++ 1y'yi C ++ 14 olarak göstermek ve C ++ 98 ve C ++ 03'ü C ++ 98 olarak göstermek için Scott Meyers'ın terminolojisini takip ediyorum, bu yüzden beni bunun için alevlendirmeyin.
  • @Mehrdad'ın yorumlarında önerildiği gibi, cevabın sonunda Canlı Örnek olarak dört uygulama sağlıyorum: C ++ 14, C ++ 11, C ++ 98 ve Boost ve C ++ 98.
  • Cevabın kendisi sadece C ++ 14 cinsinden sunulmuştur. İlgili olduğu yerde, çeşitli dil sürümlerinin farklı olduğu sözdizimsel ve kütüphane farklılıklarını belirtirim.

8
C ++ SSS etiketini soruya eklemek harika olurdu, ancak diğerlerinden en az birini kaybetmeyi gerektirecektir. Sürümleri kaldırmayı öneririm (genel bir C ++ sorusu olduğu için, bazı uyarlamalarla çoğu sürümde uygulamalar mevcuttur).
Matthieu M.

@TemplateRex Teknik olarak, eğer SSS değilse, bu soru çok geniş (tahmin - aşağı indirmedim). Btw. iyi iş, yararlı bilgiler bir sürü, teşekkürler :)
BartoszKP

Yanıtlar:


388

Algoritmik yapı taşları

Standart Kütüphane'den algoritmik yapı taşlarını birleştirerek başlıyoruz:

#include <algorithm>    // min_element, iter_swap, 
                        // upper_bound, rotate, 
                        // partition, 
                        // inplace_merge,
                        // make_heap, sort_heap, push_heap, pop_heap,
                        // is_heap, is_sorted
#include <cassert>      // assert 
#include <functional>   // less
#include <iterator>     // distance, begin, end, next
  • üye olmayan std::begin()/ std::end()gibi yineleyici araçları std::next()yalnızca C ++ 11 ve sonrası için kullanılabilir. C ++ 98 için, bunları kendisinin yazması gerekir. Orada yerine de Boost.Range gelmektedir boost::begin()/ ' boost::end()ve de Boost.Utility dan boost::next().
  • std::is_sortedAlgoritma 11 ve öbür C ++ için kullanılabilir. C ++ 98 için bu std::adjacent_findve elle yazılmış bir fonksiyon nesnesi açısından uygulanabilir . Algoritma ayrıca bir boost::algorithm::is_sorteda sağlar .
  • std::is_heapAlgoritma 11 ve öbür C ++ için kullanılabilir.

Sözdizimsel hediyeler

C ++ 14, argümanları üzerinde polimorfik olarak hareket eden formun şeffaf karşılaştırıcılarını sağlar std::less<>. Bu, bir yineleyici türü sağlamak zorunda kalmaz. Bu, karşılaştırma olarak alınan ve kullanıcı tanımlı bir karşılaştırma işlevi nesnesi olan algoritmaları sıralamak için tek bir aşırı yük oluşturmak üzere C ++ 11'in varsayılan işlev şablonu bağımsız değişkenleriyle birlikte kullanılabilir .<

template<class It, class Compare = std::less<>>
void xxx_sort(It first, It last, Compare cmp = Compare{});

C ++ 11'de, sıralama algoritmalarının imzalarına küçük bir karmaşa ekleyen bir yineleyicinin değer türünü ayıklamak için yeniden kullanılabilir bir şablon diğer adı tanımlanabilir :

template<class It>
using value_type_t = typename std::iterator_traits<It>::value_type;

template<class It, class Compare = std::less<value_type_t<It>>>
void xxx_sort(It first, It last, Compare cmp = Compare{});

C ++ 98'de, birinin iki aşırı yük yazması ve ayrıntılı typename xxx<yyy>::typesözdizimini kullanması gerekir

template<class It, class Compare>
void xxx_sort(It first, It last, Compare cmp); // general implementation

template<class It>
void xxx_sort(It first, It last)
{
    xxx_sort(first, last, std::less<typename std::iterator_traits<It>::value_type>());
}
  • Başka bir sözdizimsel özellik, C ++ 14'ün kullanıcı tanımlı karşılaştırıcıları polimorfik lambdalar ( autofonksiyon şablonu argümanları gibi çıkarılmış parametrelerle) ile sarmayı kolaylaştırmasıdır .
  • C ++ 11 sadece yukarıdaki şablon takma adının kullanılmasını gerektiren monomorfik lambdalara sahiptir value_type_t.
  • C ++ 98'de, bağımsız bir işlev nesnesi yazmak veya sözdizimi std::bind1st/ std::bind2nd/ std::not1türünün sözdizimine başvurmak gerekir .
  • Boost.Bind bunu boost::bindve _1/ _2placeholder sözdizimi ile geliştirir .
  • C ++ 11 ve ötesi de vardır std::find_if_not, oysa C ++ 98'in bir fonksiyon nesnesi etrafında ihtiyacı std::find_ifvardır std::not1.

C ++ Stili

Henüz genel olarak kabul edilebilir bir C ++ 14 stili yoktur. Daha iyisi ya da daha kötüsü, Scott Meyers'in tasarı Etkili Modern C ++ ve Herb Sutter'in yenilenmiş GotW'sini yakından takip ediyorum . Aşağıdaki stil önerilerini kullanıyorum:

  • Herb Sutter'in "Neredeyse Her Zaman Otomatik" ve Scott Meyers'ın " açıklığı bazen tartışılsa da, kısalık eşsizdir .
  • Scott Meyers'ın " Nesneleri ayırın ()ve {}oluştururken" ve {}iyi eski parantez başlangıcı yerine sürekli olarak hazır başlatmayı seçin ()(genel koddaki tüm en sinir bozucu ayrıştırma sorunlarını yan adımlamak için).
  • Scott Meyers'ın "typedefs için takma ad bildirimlerini tercih et" . Şablonlar için bu bir zorunluluktur ve bunu her yerde kullanmak typedefzaman kazandırır ve tutarlılık kazandırır.
  • for (auto it = first; it != last; ++it)Zaten sıralanmış alt aralıklar için döngü değişmez kontrol izin vermek için bazı yerlerde bir desen kullanın . Üretim kodunda, döngü içinde ve içinde while (first != last)bir ++firstyerlerde kullanım biraz daha iyi olabilir.

Seçim sıralaması

Seçim sıralaması verilere hiçbir şekilde uyum sağlamaz, bu nedenle çalışma zamanı her zaman olurO(N²). Ancak, seçim sıralaması takas sayısını en aza indirme özelliğine sahiptir. Öğeleri değiştirme maliyetinin yüksek olduğu uygulamalarda, seçim sıralaması çok iyi bir seçim algoritması olabilir.

Standart Kitaplığı kullanarak uygulamak için std::min_element, kalan minimum öğeyi bulmak ve iter_swapyerine takmak için tekrar tekrar kullanın :

template<class FwdIt, class Compare = std::less<>>
void selection_sort(FwdIt first, FwdIt last, Compare cmp = Compare{})
{
    for (auto it = first; it != last; ++it) {
        auto const selection = std::min_element(it, last, cmp);
        std::iter_swap(selection, it); 
        assert(std::is_sorted(first, std::next(it), cmp));
    }
}

selection_sortÖnceden işlenmiş aralığın [first, it)döngü değişmezi olarak sıralandığını unutmayın . Minimum gereksinimler, rasgele erişim yineleyicilerine kıyasla ileristd::sort yineleyicilerdir.

Ayrıntılar atlandı :

  • seçim sıralaması erken bir testle if (std::distance(first, last) <= 1) return;(veya ileri / çift yönlü yineleyiciler için) optimize edilebilir if (first == last || std::next(first) == last) return;.
  • için çift yönlü tekrarlayıcılara , yukarıdaki test aralığı boyunca bir döngü ile kombine edilebilir [first, std::prev(last))son eleman en az kalan eleman olması sağlanır ve bir takas gerektirmediğinden,.

Ekleme sıralaması

En O(N²)kötü zamana sahip temel sıralama algoritmalarından biri olmasına rağmen , ekleme sıralaması , veriler neredeyse sıralandığında ( uyarlanabilir olduğu için ) veya sorun boyutu küçük olduğunda (düşük ek yükü olduğu için) tercih edilen algoritmadır . Bu nedenlerden dolayı ve aynı zamanda kararlı olduğu için, birleştirme sıralaması veya hızlı sıralama gibi daha yüksek havai bölme ve fethetme sıralama algoritmaları için ekleme sıralaması genellikle özyinelemeli temel durum (sorun boyutu küçük olduğunda) olarak kullanılır.

insertion_sortStandart Kitaplık ile uygulamak std::upper_boundiçin, geçerli öğenin gitmesi gereken konumu bulmak için art arda kullanın std::rotateve kalan öğeleri giriş aralığında yukarı kaydırmak için kullanın :

template<class FwdIt, class Compare = std::less<>>
void insertion_sort(FwdIt first, FwdIt last, Compare cmp = Compare{})
{
    for (auto it = first; it != last; ++it) {
        auto const insertion = std::upper_bound(first, it, *it, cmp);
        std::rotate(insertion, it, std::next(it)); 
        assert(std::is_sorted(first, std::next(it), cmp));
    }
}

insertion_sortÖnceden işlenmiş aralığın [first, it)döngü değişmezi olarak sıralandığını unutmayın . Ekleme sıralaması, ileri yineleyicilerle de çalışır.

Ayrıntılar atlandı :

  • yerleştirme sıralaması erken bir test if (std::distance(first, last) <= 1) return;(veya ileri / çift yönlü yineleyiciler için if (first == last || std::next(first) == last) return;) ve aralık üzerinde bir döngü ile optimize edilebilir [std::next(first), last), çünkü ilk elemanın yerinde olması garanti edilir ve bir döndürme gerektirmez.
  • için çift yönlü iterators , ekleme noktasını bulmak için ikili arama bir ile değiştirilebilir ters lineer arama Standart Kütüphanesi kullanarak std::find_if_notalgoritması.

Aşağıdaki parça için dört Canlı Örnek ( C ++ 14 , C ++ 11 , C ++ 98 ve Boost , C ++ 98 ):

using RevIt = std::reverse_iterator<BiDirIt>;
auto const insertion = std::find_if_not(RevIt(it), RevIt(first), 
    [=](auto const& elem){ return cmp(*it, elem); }
).base();
  • Rastgele girişler için bu O(N²)karşılaştırmalar sağlar, ancak bu O(N)neredeyse sıralanmış girdiler için karşılaştırmaları geliştirir . İkili arama her zaman O(N log N)karşılaştırmalar kullanır .
  • Küçük giriş aralıkları için, doğrusal aramanın daha iyi bellek konumu (önbellek, önceden getirme) de ikili aramaya hâkim olabilir (elbette bunu test etmeliyiz).

Hızlı sıralama

Dikkatli bir şekilde uygulandığında, hızlı sıralama sağlamdır ve O(N log N)karmaşıklığı beklenir, ancak O(N²)tersine seçilmiş giriş verileriyle tetiklenebilecek en kötü durum karmaşıklığına sahiptir. Kararlı bir sıralama gerekli olmadığında, hızlı sıralama mükemmel bir genel amaçlı sıralamadır.

En basit sürümler için bile, hızlı sıralama Standart Kütüphane'yi kullanarak diğer klasik sıralama algoritmalarından daha karmaşıktır. Kullanımları birkaç yineleyici programları aşağıdaki yaklaşım bulmak için orta eleman giriş aralığının [first, last)ardından, eksen olarak iki çağrı kullanımı std::partition(vardır O(N)daha küçük olan elemanların parça halinde üç yönlü bölümü) giriş aralığı, e eşit, ve sırasıyla seçilen pivottan daha büyüktür. Son olarak, pivottan daha küçük ve daha büyük elemanlara sahip iki dış segment tekrar tekrar sıralanır:

template<class FwdIt, class Compare = std::less<>>
void quick_sort(FwdIt first, FwdIt last, Compare cmp = Compare{})
{
    auto const N = std::distance(first, last);
    if (N <= 1) return;
    auto const pivot = *std::next(first, N / 2);
    auto const middle1 = std::partition(first, last, [=](auto const& elem){ 
        return cmp(elem, pivot); 
    });
    auto const middle2 = std::partition(middle1, last, [=](auto const& elem){ 
        return !cmp(pivot, elem);
    });
    quick_sort(first, middle1, cmp); // assert(std::is_sorted(first, middle1, cmp));
    quick_sort(middle2, last, cmp);  // assert(std::is_sorted(middle2, last, cmp));
}

Bununla birlikte, yukarıdaki adımların her birinin üretim seviyesi kodu için dikkatlice kontrol edilmesi ve optimize edilmesi gerektiğinden, hızlı sıralama doğru ve verimli olmak için oldukça zordur. Özellikle, O(N log N)karmaşıklık için, O(1)pivot, genel olarak bir pivot için garanti edilemeyen, ancak pivotu O(N)giriş aralığının medyanı olarak ayarladığında garanti edilebilecek olan, giriş verilerinin dengeli bir bölümüyle sonuçlanmalıdır .

Ayrıntılar atlandı :

  • yukarıdaki uygulama özellikle özel girdilere karşı savunmasızdır, örneğin O(N^2)" organ borusu " girişi için karmaşıklığa sahiptir 1, 2, 3, ..., N/2, ... 3, 2, 1(çünkü orta kısım her zaman diğer tüm elemanlardan daha büyüktür).
  • Giriş aralığı korumalarından rastgele seçilen elemanlardan medyan-3- pivot seçimi, karmaşıklığın aksi takdirde bozulacağı neredeyse sıralı girişlere karşı koruma sağlarO(N^2).
  • İki çağrıda gösterildiği gibi 3-yollu bölümleme (pivottan daha küçük, ona eşit ve daha büyük elemanları ayırmak)bu sonucu elde etmekstd::partitioniçin en etkiliO(N)algoritmadeğildir.
  • için rastgele erişim yineleyicileri , garantili bir O(N log N)karmaşıklık ile elde edilebilir ortalama dönme seçimi ile std::nth_element(first, middle, last)yinelemeli çağrıları, ardından quick_sort(first, middle, cmp)ve quick_sort(middle, last, cmp).
  • Bununla birlikte, bu garantinin bir maliyeti vardır, çünkü O(N)karmaşıklığının sabit faktörü, medyan-3 pivotun karmaşıklığından std::nth_elementdaha pahalı olabilir, O(1)ardından bir O(N)çağrı std::partition(önbellek dostu tek ileri geçiş) veri).

Sıralamayı birleştir

O(N)Ek alan kullanmak endişe etmiyorsa, birleştirme sıralaması mükemmel bir seçimdir: tek kararlı O(N log N) sıralama algoritmasıdır.

Standart algoritmalar kullanarak uygulamak kolaydır: giriş aralığının ortasını bulmak için birkaç yineleyici yardımcı programını kullanın [first, last)ve yinelenen şekilde sıralanan iki segmenti aşağıdakilerle birleştirin std::inplace_merge:

template<class BiDirIt, class Compare = std::less<>>
void merge_sort(BiDirIt first, BiDirIt last, Compare cmp = Compare{})
{
    auto const N = std::distance(first, last);
    if (N <= 1) return;                   
    auto const middle = std::next(first, N / 2);
    merge_sort(first, middle, cmp); // assert(std::is_sorted(first, middle, cmp));
    merge_sort(middle, last, cmp);  // assert(std::is_sorted(middle, last, cmp));
    std::inplace_merge(first, middle, last, cmp); // assert(std::is_sorted(first, last, cmp));
}

Birleştirme sıralama çift yönlü yineleyiciler gerektirir, darboğaz std::inplace_merge. Bağlantılı listeleri sıralarken, birleştirme sıralamasının yalnızca O(log N)fazladan alan gerektirdiğini (yineleme için) unutmayın. İkinci algoritma std::list<T>::sortStandart Kütüphane'de tarafından uygulanır .

Öbek sıralaması

Öbek türünün uygulanması kolaydır,O(N log N)yerinde bir tür gerçekleştirir, ancak kararlı değildir.

İlk döngü olan O(N)"heapify" aşaması diziyi yığın sırasına sokar. İkinci döngü olan O(N log N"sortdown" aşaması, tekrar tekrar maksimumu çıkarır ve yığın sırasını geri yükler. Standart Kütüphane bunu son derece basitleştirmektedir:

template<class RandomIt, class Compare = std::less<>>
void heap_sort(RandomIt first, RandomIt last, Compare cmp = Compare{})
{
    lib::make_heap(first, last, cmp); // assert(std::is_heap(first, last, cmp));
    lib::sort_heap(first, last, cmp); // assert(std::is_sorted(first, last, cmp));
}

Kullanmanın "aldattığını" düşünürseniz std::make_heapve std::sort_heapbir seviye daha derine inebilir ve bu işlevleri sırasıyla std::push_heapve açısından kendiniz yazabilirsiniz std::pop_heap:

namespace lib {

// NOTE: is O(N log N), not O(N) as std::make_heap
template<class RandomIt, class Compare = std::less<>>
void make_heap(RandomIt first, RandomIt last, Compare cmp = Compare{})
{
    for (auto it = first; it != last;) {
        std::push_heap(first, ++it, cmp); 
        assert(std::is_heap(first, it, cmp));           
    }
}

template<class RandomIt, class Compare = std::less<>>
void sort_heap(RandomIt first, RandomIt last, Compare cmp = Compare{})
{
    for (auto it = last; it != first;) {
        std::pop_heap(first, it--, cmp);
        assert(std::is_heap(first, it, cmp));           
    } 
}

}   // namespace lib

Standart Kütüphane hem belirtir push_heapve pop_heapkarmaşıklık olarak O(log N). Not ancak aralığı üzerinde, dış döngü bu [first, last)sonuçların O(N log N)karmaşıklık make_heap, oysa std::make_heapişlem sadece O(N)karmaşıklığı. Çünkü genel O(N log N)karmaşıklığı heap_sortönemli değil.

Detaylar atlanmıştır : O(N)uygulama içindemake_heap

Test yapmak

Beş algoritmanın çeşitli girişler üzerinde test edilmesini sağlayan dört Canlı Örnek ( C ++ 14 , C ++ 11 , C ++ 98 ve Boost , C ++ 98 ) (ayrıntılı veya titiz olması amaçlanmamıştır). Sadece LOC'deki büyük farklılıklara dikkat edin: C ++ 11 / C ++ 14'ün 130 LOC, C ++ 98 ve Boost 190 (+% 50) ve C ++ 98'in 270'ten (+% 100) fazla olması gerekiyor.


13
İken ben kullanımınız katılmıyorumauto (ve birçok kişi benimle hemfikir), ben standart kütüphane algoritmalar iyi kullanılıyor görmek zevk. Sean Parent'in konuşmasını gördükten sonra bu tür kodlardan bazı örnekler görmek isterdim. Ayrıca, benim std::iter_swapiçin garip görünse de, hiçbir fikrim yoktu <algorithm>.
Joseph Mansfield

32
@sbabbi Tüm standart kitaplık, yineleyicilerin kopyalanması ucuz olduğu ilkesine dayanır; onları değere göre geçirir, mesela. Bir yineleyiciyi kopyalamak ucuz değilse, her yerde performans sorunları yaşayacaksınız.
James Kanze

2
Harika gönderi. [Std ::] 'nin hile bölümü ile ilgili olarak make_heap. Std :: make_heap hile olarak kabul edilirse, std :: push_heap de öyle. Yani hile = bir yığın yapısı için tanımlanan gerçek davranışı uygulamaz. Ben de push_heap dahil öğretici bulurdum.
Kaptan Zürafa

3
@gnzlbg Elbette yorum yapabileceğiniz varsayımlar. Erken test, yineleme kategorisi başına etiket gönderilebilir, rastgele erişim için geçerli sürümle ve if (first == last || std::next(first) == last). Bunu daha sonra güncelleyebilirim. "Atlanan ayrıntılar" bölümlerindeki öğelerin uygulanması, sorunun yanı sıra IMO'nun ötesindedir, çünkü tüm Soru ve Cevapların kendilerine bağlantılar içerirler. Gerçek kelime sıralama rutinlerini uygulamak zordur!
TemplateRex

3
Harika gönderi. Yine de, nth_elementbence kullanarak çabukort ile aldattı . nth_elementzaten bir hızlı sıralama (bölümleme adımı ve ilgilendiğiniz n'inci öğeyi içeren yarıda bir özyineleme dahil) yapar.
sellibitze

14

Başka bir küçük ve oldukça zarif bir orijinal kod inceleme bulundu . Paylaşmaya değer olduğunu düşündüm.

Sayma sıralaması

Oldukça özel olsa da, sayma sıralaması basit bir tamsayı sıralama algoritmasıdır ve sıralamak için tamsayıların değerleri birbirinden çok uzak değilse, gerçekten hızlı olabilir. Örneğin, 0 ile 100 arasında bilinen bir milyon tamsayı içeren bir koleksiyonu sıralamak gerekirse, muhtemelen idealdir.

Hem imzalı hem de imzasız tamsayılarla çalışan çok basit bir sayma türünü uygulamak için, koleksiyonun sıralamak için en küçük ve en büyük öğeleri bulması gerekir; aralarındaki fark, tahsis edilecek sayım dizisinin büyüklüğünü söyleyecektir. Daha sonra, her öğenin oluşum sayısını saymak için koleksiyondan ikinci bir geçiş yapılır. Son olarak, her tamsayı için gereken sayıyı orijinal koleksiyona geri yazıyoruz.

template<typename ForwardIterator>
void counting_sort(ForwardIterator first, ForwardIterator last)
{
    if (first == last || std::next(first) == last) return;

    auto minmax = std::minmax_element(first, last);  // avoid if possible.
    auto min = *minmax.first;
    auto max = *minmax.second;
    if (min == max) return;

    using difference_type = typename std::iterator_traits<ForwardIterator>::difference_type;
    std::vector<difference_type> counts(max - min + 1, 0);

    for (auto it = first ; it != last ; ++it) {
        ++counts[*it - min];
    }

    for (auto count: counts) {
        first = std::fill_n(first, count, min++);
    }
}

Yalnızca sıralanacak tamsayıların aralığının küçük olduğu biliniyorsa (genellikle sıralanacak koleksiyonun boyutundan daha büyük değildir), saymayı daha genel yapmak saymayı en iyi durumları için daha yavaş hale getirir. Aralığın küçük olduğu bilinmiyorsa, bunun yerine sayı tabanı sıralaması , ska_sort veya spreadsort gibi başka bir algoritma kullanılabilir.

Ayrıntılar atlandı :

  • std::minmax_elementKoleksiyondan ilk geçişten tamamen kurtulmak için algoritma tarafından kabul edilen değerler aralığının sınırlarını geçebilirdik. Bu, kullanışlı bir şekilde küçük bir aralık sınırı başka yollarla bilindiğinde algoritmayı daha da hızlandıracaktır. (Kesin olmak zorunda değil; 0 ila 100 sabitini geçmek, gerçek sınırların 1 ila 95 olduğunu bulmak için bir milyon elemanın üzerindeki ekstra geçişten hala çok daha iyidir. 0 ila 1000 bile buna değecektir; ekstra elemanlar bir kez sıfır ile yazılır ve bir kez okunur).

  • countsAnında büyümek , ayrı bir ilk geçişten kaçınmanın başka bir yoludur. countsHer büyümesi durumunda boyutun ikiye katlanması, sıralanmış öğe başına amortismanlı O (1) süre verir (üstel yetiştirmenin anahtar olduğunu kanıtlamak için karma tablo ekleme maliyet analizine bakın). Yeni sıfırlanmış elemanların eklenmesi maxile sonunda yeni bir büyüme yapmak kolaydır std::vector::resize. Değişen minanında ve ön yeni sıfırlanmış unsurlar ekleyerek ile yapılabilir std::copy_backwardvektör büyüyen sonra. Sonra std::fillyeni öğeleri sıfırlamak için.

  • countsArttırma döngü bir histogramdır. Verilerin yüksek oranda tekrarlayıcı olması ve bölme sayısının az olması durumunda, aynı bölmeye depolama / yeniden yükleme işleminin serileştirme veri bağımlılığı darboğazını azaltmak için çoklu diziler üzerinde kayıt yapmaya değer olabilir . Bu, başlangıçta sıfıra daha fazla sayım ve sonunda döngüde daha fazla sayı anlamına gelir, ancak milyonlarca 0 ila 100 sayı örneğimiz için çoğu CPU'da buna değer olmalıdır, özellikle de giriş zaten (kısmen) sıralanmışsa ve aynı sayıda uzun çalışma var.

  • Yukarıdaki algoritmada, min == maxher öğe aynı değere sahip olduğunda (bu durumda koleksiyon sıralanır) erken dönmek için bir kontrol kullanırız . Bunun yerine, ek bir zaman kaybı olmadan bir koleksiyonun uç değerlerini bulurken koleksiyonun zaten sıralanıp sıralanmadığını tam olarak kontrol etmek mümkündür (eğer ilk geçiş hala min ve maks güncelleme ekstra çalışması ile darboğazda ise). Bununla birlikte, böyle bir algoritma standart kütüphanede mevcut değildir ve bir algoritmanın yazılması, saymanın geri kalanını kendisi yazmaktan daha sıkıcı olacaktır. Okuyucu için bir egzersiz olarak bırakılmıştır.

  • Algoritma yalnızca tamsayı değerleriyle çalıştığından, kullanıcıların açık tip hatalar yapmasını önlemek için statik iddialar kullanılabilir. Bazı bağlamlarda, ile ikame başarısızlığı std::enable_if_ttercih edilebilir.

  • Modern C ++ serin olsa da, gelecekteki C ++ daha da soğuk olabilir: yapılandırılmış bağlamalar ve Ranges TS'nin bazı bölümleri algoritmayı daha da temiz hale getirir.


@TemplateRex Rasgele bir karşılaştırma nesnesi alabilseydi, saymayı bir karşılaştırma sıralaması yapar ve karşılaştırma sıraları O (n log n) 'den daha kötü bir duruma sahip olamaz. Sayım sıralamasının en kötü O (n + r) durumu vardır, yani zaten bir karşılaştırma türü olamaz. Tamsayılar olabilir karşılaştırılabilir ancak bu özellik (yalnızca kullanılan sıralama yapılması kullanılmaz std::minmax_elementsadece bilgi toplar olan). Kullanılan özellik, tamsayıların indeksler veya ofsetler olarak kullanılabilmeleri ve ikinci özelliği korurken artırılabilir olmalarıdır.
Morwenn

Aralıkları TS gerçekten çok güzel, örneğin son döngü bitmiş olabilir, counts | ranges::view::filter([](auto c) { return c != 0; })böylece içindeki sıfır olmayan sayıları tekrar tekrar test etmeniz gerekmez fill_n.
TemplateRex

(I yazım hatalarını tespit small bir rather ve appart- Ben düzenleme konusunda reggae_sort til onları tutmak olabilir?)
ihtiyar adam

@greybeard Ne istersen yapabilirsin: p
Morwenn

counts[]Anında büyümenin minmax_element, histogramlamadan önce girdinin üzerinden geçmesi karşısında bir kazanç olacağından şüpheleniyorum . Özellikle bunun ideal olduğu kullanım durumunda, küçük bir aralıkta birçok tekrarlanan çok büyük girdiden dolayı counts, birkaç şube yanlış tahminleri veya boyut iki katına çıkarak hızla tam boyutuna büyüyeceksiniz . (Tabii ki, dizi bağlanan küçük yeterince bilmeden bir kaçının sağlayacak minmax_elementtarama ve önlemek histogram döngü içinde sınırları denetimi.)
Peter Cordes
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.