Yerinde Radix Sıralaması


201

Bu uzun bir metindir. Lütfen bana eşlik et. Haşlanmış soru şu: Çalışılabilir bir yerinde radyant sıralama algoritması var mı?


Ön hazırlık

Sıralamak istediğim sadece “A”, “C”, “G” ve “T” harflerini kullanan çok sayıda sabit uzunluklu dizgim var (evet, tahmin ettiniz: DNA ).

Şu anda kullandığım std::sortkullandığı içgözlemle sıralama tüm ortak uygulamalarda STL . Bu gayet iyi çalışıyor. Ancak, sayı tabanı sıralamasının sorun kümeme mükemmel bir şekilde uyduğuna ve uygulamada daha iyi çalışması gerektiğine inanıyorum .

ayrıntılar

Bu varsayımı çok naif bir uygulama ile test ettim ve nispeten küçük girdiler için (10.000 civarında) bu doğruydu (en az iki kat daha hızlı). Ancak, sorun boyutu büyüdüğünde ( N > 5.000.000) çalışma zamanı uçuruma uğrar .

Nedeni açıktır: sayı tabanı sıralaması tüm verilerin kopyalanmasını gerektirir (aslında benim naif uygulamamda birden fazla). Bu, ~ 4 GiB'yi ana belleğime koyduğum anlamına geliyor ve bu da performansı öldürüyor. Olmasa bile, sorun büyüklükleri daha da büyüdüğü için bu kadar belleği kullanamıyorum.

Kullanım Örnekleri

İdeal olarak, bu algoritma DNA ve DNA5 (ek bir joker karakter “N” karakterine izin verir) ve hatta IUPAC belirsizlik kodlu DNA (16 farklı değerle sonuçlanır ) için 2 ile 100 arasında herhangi bir dize uzunluğu ile çalışmalıdır . Ancak, tüm bu vakaların karşılanamayacağının farkındayım, bu yüzden aldığım herhangi bir hız iyileştirmesinden memnunum. Kod, hangi algoritmanın gönderileceğine dinamik olarak karar verebilir.

Araştırma

Ne yazık ki, sayı tabanı sıralaması hakkındaki Wikipedia makalesi işe yaramaz. Yerinde bir varyant ile ilgili bölüm tam bir çöptür. Radix üzerine NIST-BABALAR bölüm biraz varolmayan yanındadır. “MSL” algoritmasını açıklayan Efficient Adaptive In-Place Radix Sorting adında gelecek vaat eden bir kağıt var . Ne yazık ki, bu yazı da hayal kırıklığı yaratıyor.

Özellikle, aşağıdaki şeyler vardır.

İlk olarak, algoritma birkaç hata içeriyor ve çok fazla açıklanmıyor. Özellikle, özyineleme çağrısını ayrıntılandırmaz (sadece geçerli kaydırma ve maske değerlerini hesaplamak için bazı işaretçiyi arttırdığını veya azalttığını varsayıyorum). Ayrıca, işlevlerini kullanır dest_groupve dest_addresstanımları vermeden. Bunların verimli bir şekilde nasıl uygulanacağını göremiyorum (yani O (1); en azından dest_addressönemsiz değil).

Sonuncu fakat son derece önemli olarak, algoritma dizi indekslerini girdi dizisinin içindeki elemanlarla değiştirerek yerinde gerçekleşir. Bu sadece sayısal dizilerde işe yarar. Dizelerde kullanmam gerekiyor. Tabii ki, güçlü yazmayı vidalayabilirim ve hafızanın ait olmadığı bir dizini depolamamı tolere edeceğini varsayarak devam edebilirim. Ancak bu sadece dizelerimi 32 bit belleğe sıkıştırabildiğim sürece çalışır (32 bit tamsayı varsayarak). Bu sadece 16 karakterdir (16> günlüğü (5.000.000) olduğu için yok sayalım).

Yazarlardan birinin başka bir makalesinde kesin bir açıklama yoktur, ancak MSL'nin çalışma zamanını alt doğrusal olarak verir ve bu da yanlıştır.

Özetlemek gerekirse : DNA dizeleri üzerinde çalışan bir çalışma referans uygulaması veya en azından iyi bir sözde kod / çalışan yerinde bir sayı tabanı açıklaması bulma umudu var mı?


65
Bu mükemmel yazılmış bir soru.
JustinT

1
küçük sabit uzunluklu teller ne kadar küçük?
EvilTeach

1
@EvilTeach: Kullanım örneklerini ekledim.
Konrad Rudolph

2
@Stephan: Hepsi iyi ve iyi. Ancak kopyalama / önbellek özledim durumunda sadece bir gecikme olur. Hafıza durumunda, fiziksel bir sınıra girdim. Bu sadece müzakere edilemez. Verilerin bir bölümünü diskte saklamak için kullanılan tüm bu fantezi teknikler, mevcut hızlı sıralama çözümünden kesinlikle daha yavaştır.
Konrad Rudolph

2
(devamı ') dsimcha'nın çözümü, bazı girdiler için kesinlikle hızlı sıralamadan daha hızlıdır . Hareket sayısı yüksek ve önbellek yöresi az olabilir, ancak gerçek dünyada hala iyidir. Ayrıca, yapmam gereken takas sayısını azaltmak için çözümü biraz değiştirdim.
Konrad Rudolph

Yanıtlar:


61

DNA için bir MSD radyant sıralaması basit bir uygulaması. D dilinde yazılmıştır, çünkü en çok kullandığım dildir ve bu nedenle aptalca hatalar yapma olasılığı en düşüktür, ancak kolayca başka bir dile çevrilebilir. Yerinde ama 2 * seq.lengthdizi üzerinden geçiş gerektirir .

void radixSort(string[] seqs, size_t base = 0) {
    if(seqs.length == 0)
        return;

    size_t TPos = seqs.length, APos = 0;
    size_t i = 0;
    while(i < TPos) {
        if(seqs[i][base] == 'A') {
             swap(seqs[i], seqs[APos++]);
             i++;
        }
        else if(seqs[i][base] == 'T') {
            swap(seqs[i], seqs[--TPos]);
        } else i++;
    }

    i = APos;
    size_t CPos = APos;
    while(i < TPos) {
        if(seqs[i][base] == 'C') {
            swap(seqs[i], seqs[CPos++]);
        }
        i++;
    }
    if(base < seqs[0].length - 1) {
        radixSort(seqs[0..APos], base + 1);
        radixSort(seqs[APos..CPos], base + 1);
        radixSort(seqs[CPos..TPos], base + 1);
        radixSort(seqs[TPos..seqs.length], base + 1);
   }
}

Açıkçası, bu genel olmanın aksine DNA'ya özgüdür, ancak hızlı olmalıdır.

Düzenle:

Bu kodun gerçekten çalışıp çalışmadığını merak ettim, bu yüzden kendi biyoinformatik kodumun çalışmasını beklerken test ettim / hata ayıkladım. Yukarıdaki sürüm aslında test edildi ve çalışıyor. Her biri 5 bazdan 10 milyon dizi için, optimize edilmiş bir introorttan yaklaşık 3 kat daha hızlıdır.


9
2x geçişli bir yaklaşımla yaşayabiliyorsanız, bu sayı taban-N'ye uzanır: geçiş 1 = sadece ilerleyin ve N basamaklarının her birinin kaç tane olduğunu sayın. Daha sonra diziyi bölümlere ayırıyorsanız, her bir basamağın nereden başladığını gösterir. Pass 2, dizideki uygun konuma takas yapar.
Jason S

(örneğin, N = 4 için, 90000 A, 80000 G, 100 C, 100000 T varsa, APos'unuzun yerine kullanılan toplamlar = [0, 90000, 170000, 170100] olarak başlatılmış bir dizi yapın, her rakam için bir sonraki eleman ile takas olması gereken yerde bir imleç olarak vb CPOs).
Jason S

İkili gösterimle bu dize gösterimi arasındaki ilişkinin, en az 4 kat daha fazla bellek kullanmanın dışında ne olacağından emin değilim
Stephan Eggermont

Daha uzun sekanslarla hız nasıl? 5 uzunluğu ile yeterince farklı olan yok
Stephan Eggermont

4
Bu sayı tabanı sıralaması, iyi bilinen bir yerinde sayı tabanı sıralaması türü olan Amerikan Bayrağı sıralamasının özel bir durumu gibi görünmektedir.
Edward KMETT

21

Asla yerinde bir sayı tabanı sıralaması görmedim ve sayı tabanı sıralamasının doğası gereği geçici dizi belleğe sığdığı sürece yer dışında sıra çok daha hızlı olduğundan şüphe.

Sebep:

Sıralama, girdi dizisinde doğrusal bir okuma yapar, ancak tüm yazma işlemleri neredeyse rasgele olur. Belli bir N'den itibaren, bu yazma başına önbellek kaçışına kadar kaynar. Bu önbellek özledim algoritmanızı yavaşlatan şeydir. Yerinde veya yerinde değilse bu etki değişmez.

Bunun doğrudan sorunuza cevap vermeyeceğini biliyorum, ancak sıralama bir darboğaz ise ön işleme adımı olarak yakın sıralama algoritmalarına bir göz atmak isteyebilirsiniz (yumuşak yığındaki wiki sayfası başlayabilir).

Bu çok güzel bir önbellek yer artışı sağlayabilir. Bu durumda bir metin kitabı yersiz dış taban sıralaması daha iyi performans gösterir. Yazmalar hala neredeyse rastgele olacak, ancak en azından aynı bellek yığınları etrafında kümelenecek ve böylece önbellek isabet oranını artıracak.

Yine de pratikte işe yarayıp yaramadığı hakkında hiçbir fikrim yok.

Btw: Yalnızca DNA dizeleriyle uğraşıyorsanız: Bir karakteri iki bite sıkıştırabilir ve verilerinizi çok fazla paketleyebilirsiniz. Bu, naif bir sunum üzerinde bellek gereksinimini dördüncü faktör azaltacaktır. Adresleme daha karmaşık hale gelir, ancak CPU'nuzun ALU'sunun tüm önbellek özledikleri sırasında harcayacak çok zamanı vardır.


2
İki iyi nokta; neredeyse sıralama benim için yeni bir kavram, bunu okumak zorundayım. Önbellek özlüyor, rüyalarımı rahatsız eden bir başka nokta. ;-) Bunu görmem gerekecek.
Konrad Rudolph

Benim için de yeni (birkaç ay), ama bir kez konsept sahibi olduktan sonra performans geliştirme fırsatlarını görmeye başlıyorsunuz.
Nils Pipenbrinck

Sayı tabanı çok büyük olmadıkça, yazma işlemleri neredeyse rastgele değildir . Örneğin, her seferinde bir karakter (sayı tabanı-4 sıralaması) sıraladığınız varsayılarak, tüm yazılar doğrusal olarak büyüyen 4 kovadan birine ait olacaktır. Bu hem önbellek hem de önceden getirme dostudur. Tabii ki, daha büyük bir yarıçap kullanmak isteyebilirsiniz ve bazı işaretçilerde önbellek ve önceden getirme dostu ve yarıçap boyutu arasında bir dengeye ulaşırsınız. "Gerçek" kovalara periyodik olarak kızarma yapan kovalarınız için yazılım ön alma veya çizik alanı kullanarak başabaş noktasını daha büyük radyasyonlara doğru itebilirsiniz.
BeeOnRope

8

Diziyi bit cinsinden kodlayarak bellek gereksinimlerini kesinlikle düşürebilirsiniz. Böylece permütasyonlara bakıyorsunuz, uzunluk 2 için, 16 durum veya "4 bit" ACGT "ile. Uzunluk 3 için bu, 6 bit olarak kodlanabilen 64 durumdur. Yani, dizideki her harf için 2 bit veya dediğin gibi 16 karakter için yaklaşık 32 bit gibi görünüyor.

Geçerli 'kelime' sayısını azaltmanın bir yolu varsa, daha fazla sıkıştırma mümkün olabilir.

Bu nedenle, uzunluk 3 dizileri için, 64 kova, belki uint32 veya uint64 boyutlu olabilir. Bunları sıfıra başlatın. Çok büyük 3 karakter dizisi listenizi yineleyin ve yukarıdaki gibi kodlayın. Bunu bir alt simge olarak kullanın ve bu grubu artırın.
Tüm sekanslarınız işlenene kadar bunu tekrarlayın.

Ardından, listenizi yeniden oluşturun.

Bu kovada bulunan sayım için, bu kova tarafından temsil edilen sıralamanın birçok örneğini oluşturmak için 64 kovadan tekrarlayın.
tüm kovalar yinelendiğinde, sıralı diziniz olur.

4'lük bir dizi, 2 bit ekler, böylece 256 kova olur. 5 dizisi 2 bit ekler, bu yüzden 1024 kova olacaktır.

Bir noktada kova sayısı sınırlarınıza yaklaşacaktır. Sekansları bir dosyadan okursanız, onları bellekte tutmak yerine, kovalar için daha fazla bellek kullanılabilir olacaktır.

Kovaların çalışma setinize sığması muhtemel olduğundan, bu işi yerinde yapmaktan daha hızlı olacağını düşünüyorum.

İşte tekniği gösteren bir kesmek

#include <iostream>
#include <iomanip>

#include <math.h>

using namespace std;

const int width = 3;
const int bucketCount = exp(width * log(4)) + 1;
      int *bucket = NULL;

const char charMap[4] = {'A', 'C', 'G', 'T'};

void setup
(
    void
)
{
    bucket = new int[bucketCount];
    memset(bucket, '\0', bucketCount * sizeof(bucket[0]));
}

void teardown
(
    void
)
{
    delete[] bucket;
}

void show
(
    int encoded
)
{
    int z;
    int y;
    int j;
    for (z = width - 1; z >= 0; z--)
    {
        int n = 1;
        for (y = 0; y < z; y++)
            n *= 4;

        j = encoded % n;
        encoded -= j;
        encoded /= n;
        cout << charMap[encoded];
        encoded = j;
    }

    cout << endl;
}

int main(void)
{
    // Sort this sequence
    const char *testSequence = "CAGCCCAAAGGGTTTAGACTTGGTGCGCAGCAGTTAAGATTGTTT";

    size_t testSequenceLength = strlen(testSequence);

    setup();


    // load the sequences into the buckets
    size_t z;
    for (z = 0; z < testSequenceLength; z += width)
    {
        int encoding = 0;

        size_t y;
        for (y = 0; y < width; y++)
        {
            encoding *= 4;

            switch (*(testSequence + z + y))
            {
                case 'A' : encoding += 0; break;
                case 'C' : encoding += 1; break;
                case 'G' : encoding += 2; break;
                case 'T' : encoding += 3; break;
                default  : abort();
            };
        }

        bucket[encoding]++;
    }

    /* show the sorted sequences */ 
    for (z = 0; z < bucketCount; z++)
    {
        while (bucket[z] > 0)
        {
            show(z);
            bucket[z]--;
        }
    }

    teardown();

    return 0;
}

Hash eh yapabildiğinizde neden karşılaştırmalısınız?
zayıf

1
Lanet olsun. Performans genellikle herhangi bir DNA işlemiyle ilgili bir sorundur.
EvilTeach

6

Veri kümeniz çok büyükse, o zaman disk tabanlı bir tampon yaklaşım en iyi olacağını düşünürdüm:

sort(List<string> elements, int prefix)
    if (elements.Count < THRESHOLD)
         return InMemoryRadixSort(elements, prefix)
    else
         return DiskBackedRadixSort(elements, prefix)

DiskBackedRadixSort(elements, prefix)
    DiskBackedBuffer<string>[] buckets
    foreach (element in elements)
        buckets[element.MSB(prefix)].Add(element);

    List<string> ret
    foreach (bucket in buckets)
        ret.Add(sort(bucket, prefix + 1))

    return ret

Ayrıca, eğer dize:

GATTACA

ilk MSB çağrısı GATT (toplam 256 kova) için kovayı döndürür, böylece disk tabanlı tamponun daha az dalını yaparsınız. Bu performansı artırabilir veya artırmayabilir, bu nedenle deneyin.


Bazı uygulamalar için bellek eşlemeli dosyalar kullanıyoruz. Bununla birlikte, genel olarak, makinenin açık disk desteği gerektirmeyecek kadar az RAM sağladığı varsayımıyla çalışıyoruz (elbette, takas hala gerçekleşiyor). Ama zaten otomatik disk destekli diziler için bir mekanizma geliştiriyoruz
Konrad Rudolph

6

Bir uzuv çıkacağım ve bir yığın / heapsort uygulamasına geçmenizi öneririm . Bu öneri bazı varsayımlarla birlikte gelir:

  1. Verilerin okunmasını kontrol edersiniz
  2. Sıralamaya başladıktan hemen sonra sıralanan verilerle anlamlı bir şey yapabilirsiniz.

Yığın / yığın sıralamasının güzelliği, verileri okurken yığını oluşturabilmeniz ve yığını oluşturduğunuz anda sonuç almaya başlayabilmenizdir.

Geri çekilelim. Verileri eşzamansız olarak okuyabileceğiniz için çok şanslıysanız (yani, bir tür okuma isteği gönderebilir ve bazı veriler hazır olduğunda bildirimde bulunabilirsiniz) ve daha sonra beklerken öbek yığını oluşturabilirsiniz. bir sonraki veri parçası - diskten bile. Genellikle, bu yaklaşım, verilerinizi elde etmek için harcanan sürenin ardındaki sıralamanızın yarısının çoğunu gömebilir.

Verileri okuduktan sonra, ilk öğe zaten kullanılabilir. Verileri nereye gönderdiğinize bağlı olarak, bu harika olabilir. Başka bir eşzamansız okuyucuya ya da paralel bir 'olay' modeline ya da kullanıcı arayüzüne gönderiyorsanız, istediğiniz gibi parçalar ve parçalar gönderebilirsiniz.

Bununla birlikte, verinin nasıl okunacağı konusunda herhangi bir kontrole sahip değilseniz ve senkronize olarak okunuyorsa ve tamamen yazılana kadar sıralanan veriler için hiçbir kullanımınız yoksa - tüm bunları göz ardı edin. :(

Wikipedia makalelerine bakın:


1
İyi öneri. Ancak, bunu zaten denedim ve özel durumumda, bir yığın tutmanın yükü sadece bir vektörde veri biriktirmek ve tüm veriler geldiğinde sıralamaktan daha büyük.
Konrad Rudolph


4

Performans açısından, daha genel bir dize karşılaştırma sıralama algoritmalarına bakmak isteyebilirsiniz.

Şu anda her ipin her elemanına dokunuyorsunuz, ancak daha iyisini yapabilirsiniz!

Özellikle, bir patlama türü bu durum için çok iyi bir seçimdir. Bonus olarak, burstsort denemelere dayandığından, DNA / RNA'da kullanılan küçük alfabe boyutları için gülünç derecede iyi çalışır, çünkü herhangi bir üçlü arama düğümü, karma veya diğer üçlü düğüm sıkıştırma şeması oluşturmanıza gerek yoktur. trie uygulaması. Denemeler, sonek dizisi benzeri nihai hedefiniz için de yararlı olabilir.

Burstsort'un iyi bir genel amaçlı uygulaması, http://sourceforge.net/projects/burstsort/ adresindeki kaynak dövüşünde mevcuttur - ancak yerinde değildir.

Karşılaştırma amacıyla, C-patlama uygulaması http://www.cs.mu.oz.au/~rsinha/papers/SinhaRingZobel-2006.pdf bazı tipik iş yükleri için hızlı sıralama ve sayı tabanı sıralamasından 4-5 kat daha hızlı bir şekilde ele alınmıştır.


Kesinlikle patlama türüne bakmak zorundayım - şu anda trie'nin nasıl inşa edileceğini görmüyorum. Genel olarak sonek dizileri, pratik uygulamalardaki üstün performans özellikleri nedeniyle biyoenformatikte yer alan son ek ağaçlarının (ve dolayısıyla denemelerinin) dışında yer alır.
Konrad Rudolph

4

Drs'ın Büyük Ölçekli Genom Dizisi İşlemesine bir göz atmak isteyeceksiniz . Kasahara ve Morishita.

Dört nükleotit harfinden oluşan A, C, G ve T dizeleri çok daha hızlı işlem için Tamsayılara özel olarak kodlanabilir . Radix sıralaması kitapta tartışılan birçok algoritma arasındadır; kabul edilen cevabı bu soruya uyarlayabilmeli ve büyük bir performans artışı görmelisiniz.


Bu kitapta sunulan sayı tabanı sıralaması yerinde değildir, bu nedenle bu amaç için kullanılamaz. Dize sıkıştırma gelince, ben (tabii ki) zaten yapıyorum. Benim (az ya da çok) son çözümüm (aşağıda yayınlanan) bunu göstermiyor çünkü kütüphane bunları normal dizeler gibi ele almamı sağlıyor - ama RADIXkullanılan değer elbette daha büyük değerlere uyarlanabilir (ve öyle).
Konrad Rudolph

3

Bir trie kullanmayı deneyebilirsiniz . Verileri sıralamak sadece veri kümesi üzerinden yineleniyor ve ekleniyor; yapı doğal olarak sıralanır ve bunu bir B-Ağacına benzer olarak düşünebilirsiniz (karşılaştırmalar yapmak yerine, her zaman işaretçi indirimlerini kullanırsınız).

Önbellek davranışı tüm iç düğümleri destekleyecektir, bu nedenle muhtemelen bunu geliştiremezsiniz; ancak kendi üçgenin dallanma faktörü ile de uğraşabilirsiniz (her düğümün tek bir önbellek satırına sığdığından emin olun, bir yığına benzer üç düğümleri, bir düzeye göre geçişi temsil eden bitişik bir dizi olarak tahsis edin). Denemeler aynı zamanda k uzunluğundaki elemanlar için dijital yapılar (O (k) ekleme / bulma / silme) olduğundan, bir sayı tabanı sıralamasında rekabetçi performansınız olmalıdır.


Üçlü, benim saf uygulama ile aynı soruna sahiptir: O (n) ek bellek gerektirir ki bu çok fazla.
Konrad Rudolph

3

Ben ediyorum burstsort dizeleri paketlenmiş bit temsilini. Burstsort'un radyus çeşitlerinden çok daha iyi bir yere sahip olduğu ve klasik denemeler yerine patlama denemeleriyle ekstra alan kullanımını azalttığı iddia ediliyor. Orijinal kağıdın ölçümleri vardır.


2

Radix-Sort önbellekte bilinçli değildir ve büyük kümeler için en hızlı sıralama algoritması değildir. Şunlara bakabilirsiniz:

Ayrıca sıralama dizisine kaydetmeden önce sıkıştırma kullanabilir ve DNA'nızın her harfini 2 bit kodlayabilirsiniz.


bill: Bu qsortişlevin std::sortC ++ tarafından sağlanan işleve göre ne gibi avantajları olduğunu açıklayabilir misiniz ? Özellikle, sonuncusu modern kütüphanelerde oldukça karmaşık bir introsort uygular ve karşılaştırma işlemini sıralar. Çoğu durumda O (n) 'de gerçekleştirdiği iddiasını satın almıyorum, çünkü bu genel durumda mevcut olmayan bir içgözlem derecesi gerektirecektir (en azından çok fazla yük olmadan ).
Konrad Rudolph

C ++ kullanmıyorum, ancak testlerimde satır içi QSORT, stdlib'deki qsort'tan 3 kat daha hızlı olabilir. Ti7qsort, tamsayılar için en hızlı sıralamadır (satır içi QSORT'dan daha hızlı). Küçük boyutlu sabit verileri sıralamak için de kullanabilirsiniz. Testleri verilerinizle yapmalısınız.
Tasarı

1

dsimcha'nın MSB radix sıralaması güzel görünüyor, ancak Nils, önbellek yerinin büyük sorun boyutlarında sizi öldüren şey olduğu gözlemiyle sorunun kalbine yaklaşıyor.

Çok basit bir yaklaşım öneriyorum:

  1. Ampirik olarak en büyük boyutu tahmin edin mBir sayı tabanı sıralamasının etkili olduğu .
  2. Her mseferinde eleman bloklarını okuyun , sayı tabanı sıralayın ve girişinizi bitirinceye kadar bunları (yeterli belleğiniz varsa, ancak başka bir dosya için bir bellek arabelleğine) yazın.
  3. Mergesort Elde edilen kriteri blok.

Mergesort, farkında olduğum en önbellek dostu sıralama algoritmasıdır: "A veya B dizisinden sonraki öğeyi okuyun, sonra çıktı arabelleğine bir öğe yazın." Teyp sürücülerinde verimli çalışır . Öğeleri 2nsıralamak için yer gerektirir n, ancak bahse gireceğim çok gelişmiş önbellek konumunun önemsiz olmasını sağlayacaktır - ve yerinde olmayan bir radix sıralaması kullanıyorsanız, yine de bu ekstra alana ihtiyacınız vardı.

Son olarak, birleştirme işleminin özyineleme olmadan uygulanabileceğini ve aslında bu şekilde gerçekleştirmenin gerçek doğrusal bellek erişim düzenini netleştirdiğini lütfen unutmayın.


1

Sorunu çözdüğünüz anlaşılıyor, ancak kayıt için, uygulanabilir bir yerinde sayı tabanı sıralamasının bir sürümünün "Amerikan Bayrağı Sıralaması" olduğu anlaşılıyor. Burada açıklanmıştır: Engineering Radix Sort . Genel fikir, her karakterde 2 geçiş yapmaktır - önce her birinin kaç tanesine sahip olduğunuzu sayın, böylece giriş dizisini bölmelere ayırabilirsiniz. Ardından, her öğeyi doğru bölmeye yerleştirerek tekrar geçin. Şimdi her bölmeyi bir sonraki karakter konumuna özyineli olarak sıralayın.


Aslında, kullandığım çözüm Bayrak Sıralama algoritmasıyla çok yakından ilgilidir. İlgili bir ayrım olup olmadığını bilmiyorum.
Konrad Rudolph

2
Amerikan Bayrağı Sıralaması'nı hiç duymadım, ama görünüşe göre kodladığım şey: coliru.stacked-crooked.com/a/94eb75fbecc39066 Şu anda daha iyi performans gösteriyor std::sortve çok basamaklı bir sayısallaştırıcının hala daha hızlı gidebileceğinden eminim, ancak test takımımın belleği var problemler (algoritma değil, test takımının kendisi değil)
Mooing Duck

@KonradRudolph: Bayrak sıralaması ve diğer sayı tabanı sıralamaları arasındaki en büyük fark sayma geçişidir. Tüm sayı tabanı türlerinin çok yakından ilişkili olduğu konusunda haklısın, ama seninkini bir Bayrak türü olarak görmem
Mooing Duck

@MooingDuck: Oradaki örneğinizden biraz ilham aldım - kendi bağımsız uygulamamda takılı kaldım ve sizinki tekrar yola çıkmama yardımcı oldu. Teşekkürler! Olası bir optimizasyon - Henüz buna değip değmeyeceğini görmek için yeterince fazla bir şey elde etmedim: Değiştirmeyi düşündüğünüz pozisyondaki öğe zaten olması gereken yerde oluyorsa, bunu atlayabilir ve değil. Bunu tespit etmek elbette ekstra mantık ve olası ekstra depolama gerektirir, ancak takasların karşılaştırmaya göre pahalı olması nedeniyle yapmaya değer olabilir.
500 - Dahili Sunucu Hatası

1

İlk olarak, probleminizin kodlanmasını düşünün. Dizelerden kurtulun, ikili bir temsil ile değiştirin. Uzunluk + kodlamayı belirtmek için ilk baytı kullanın. Alternatif olarak, dört baytlık bir sınırda sabit uzunluklu bir gösterim kullanın. Sonra sayı tabanı sıralaması çok daha kolay hale gelir. Bir sayı tabanı sıralaması için, en önemli şey iç halkanın sıcak noktasında istisna işlemesi olmamalıdır.

Tamam, ben 4-nary sorunu hakkında biraz daha düşündüm. Bunun için Judy ağacı gibi bir çözüm istiyorsun . Bir sonraki çözüm değişken uzunluklu dizeleri işleyebilir; sabit uzunluk için sadece uzunluk bitlerini çıkarın, bu aslında daha kolay hale getirir.

16 işaretlik bloklar tahsis edin. Bloklarınız her zaman hizalanacağından, işaretçilerin en az önemli kısmı yeniden kullanılabilir. Bunun için özel bir depolama ayırıcı isteyebilirsiniz (büyük depolama alanını daha küçük bloklara ayırın). Bir dizi farklı blok vardır:

  • Değişken uzunlukta dizelerden oluşan 7 uzunluk biti ile kodlama. Doldurdukça onları değiştirirsiniz:
  • Pozisyon sonraki iki karakteri kodlar, bir sonraki bloklara 16 işaretçiniz vardır ve biter:
  • Bir dizenin son üç karakterinin bitmap kodlaması.

Her bir blok türü için LSB'lerde farklı bilgiler depolamanız gerekir. Değişken uzunluklu dizeleriniz olduğundan, dize sonu da saklamanız gerekir ve son blok türü yalnızca en uzun dizeler için kullanılabilir. Yapıyı derinleştirdikçe 7 uzunluk biti daha az ile değiştirilmelidir.

Bu, sıralı dizelerin makul derecede hızlı ve çok bellek verimli bir şekilde depolanmasını sağlar. Bir şekilde bir üçlü gibi davranacak . Bunu yapmak için, yeterli birim testleri yaptığınızdan emin olun. Tüm blok geçişlerinin kapsamını istiyorsunuz. Sadece ikinci tür blokla başlamak istiyorsunuz.

Daha da fazla performans için, farklı blok türleri ve daha büyük bir blok boyutu eklemek isteyebilirsiniz. Bloklar her zaman aynı boyutta ve yeterince büyükse, işaretçiler için daha az bit kullanabilirsiniz. 16 işaretçi blok boyutu ile, 32 bit adres alanında zaten bir bayt boş var. İlginç blok türleri için Judy ağacı belgelerine bir göz atın. Temel olarak, bir alan (ve çalışma zamanı) ödünleşimi için kod ve mühendislik zamanı eklersiniz

Muhtemelen ilk dört karakter için 256 genişliğinde doğrudan bir yarıçapla başlamak istersiniz. Bu, iyi bir alan / zaman dengesi sağlar. Bu uygulamada, basit bir üçgenden daha az bellek yükü elde edersiniz; yaklaşık üç kat daha küçüktür (ölçmedim). O (n) n hızlı nort ile karşılaştırırken fark ettiğiniz gibi, sabit yeterince düşükse O (n) sorun yaratmaz.

Çiftleri ele almakla ilgileniyor musunuz? Kısa dizilerle, olacak. Blokları sayıları işleyecek şekilde uyarlamak zordur, ancak yerden tasarruf sağlayabilir.


Biraz paketlenmiş bir gösterim kullanırsam, tabanım sıralamasının nasıl daha kolay hale geldiğini görmüyorum. Bu arada, kullandığım çerçeve aslında biraz paketlenmiş bir temsil kullanma olanağı sunuyor, ancak bu benim için arayüzün bir kullanıcısı olarak tamamen şeffaf.
Konrad Rudolph

Kronometrenize baktığınızda değil :)
Stephan Eggermont

Judy ağaçlarına kesinlikle bir göz atacağım. Vanilya, masaya gerçekten fazla bir şey getirmez, çünkü temelde elemanların üzerinden daha az geçişle normal bir MSD yarıçapı gibi davranırlar, ancak ekstra depolama gerektirirler.
Konrad Rudolph
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.