इन-प्लेस रेडिक्स सॉर्ट


200

यह एक लंबा पाठ है। कृपया मेरा साथ दें। उबला हुआ, सवाल यह है: क्या एक जगह में काम योग्य रेडिक्स सॉर्ट एल्गोरिथ्म है ?


प्रारंभिक

मुझे बड़ी संख्या में छोटे-छोटे निश्चित स्ट्रिंग्स मिले हैं जो केवल "ए", "सी", "जी" और "टी" (हां, आपने अनुमान लगाया है: डीएनए ) अक्षरों का उपयोग करते हैं, जिन्हें मैं सॉर्ट करना चाहता हूं।

पल में, मैं का उपयोग करें std::sortजो उपयोग करता introsort के सभी आम कार्यान्वयन में एसटीएल । यह काफी अच्छा काम करता है। हालाँकि, मुझे विश्वास है कि मूलांक मेरी समस्या को पूरी तरह से सेट करता है और व्यवहार में बहुत बेहतर काम करना चाहिए ।

विवरण

मैंने इस धारणा को बहुत भोली कार्यान्वयन के साथ और अपेक्षाकृत छोटे इनपुट के लिए (10,000 के आदेश पर) परीक्षण किया है यह सच था (ठीक है, कम से कम दो बार के रूप में तेजी से)। हालाँकि, समस्या का आकार बड़ा होने पर रनटाइम अपमानजनक रूप से कम हो जाता है ( N > 5,000,000)।

कारण स्पष्ट है: मूलांक की तरह पूरे डेटा की प्रतिलिपि बनाने की आवश्यकता है (वास्तव में मेरे भोले कार्यान्वयन में एक से अधिक बार, वास्तव में)। इसका मतलब है कि मैंने अपनी मुख्य मेमोरी में ~ 4 GiB डाल दिया है जो जाहिर तौर पर प्रदर्शन को मारता है। यहां तक ​​कि अगर यह नहीं था, मैं वास्तव में भी बड़ा हो समस्या आकार के बाद से इस स्मृति का उपयोग करने के लिए बर्दाश्त नहीं कर सकता।

बक्सों का इस्तेमाल करें

आदर्श रूप से, यह एल्गोरिथ्म डीएनए और साथ ही डीएनए 5 (जो एक अतिरिक्त वाइल्डकार्ड वर्ण "एन") की अनुमति देता है, या यहां तक ​​कि आईयूपीएसी अस्पष्टता कोड (जिसके परिणामस्वरूप 16 अलग-अलग मान हैं) के साथ डीएनए के लिए किसी भी स्ट्रिंग लंबाई के साथ काम करना चाहिए । हालाँकि, मुझे एहसास है कि इन सभी मामलों को कवर नहीं किया जा सकता है, इसलिए मैं किसी भी गति सुधार से खुश हूं जो मुझे मिलता है। कोड गतिशील रूप से तय कर सकता है कि किस एल्गोरिथ्म को प्रेषण करना है।

अनुसंधान

दुर्भाग्य से, मूलांक के आधार पर विकिपीडिया लेख बेकार है। इन-प्लेस वैरिएंट के बारे में अनुभाग पूरी तरह से बकवास है। मूलांक पर NIST-पिता अनुभाग प्रकार अस्तित्वहीन के बगल में है। वहाँ एक आशाजनक-लगने वाला कागज है जिसे कुशल एडेप्टिव-इन-प्लेस रेडिक्स सॉर्टिंग कहा जाता है जो एल्गोरिथ्म "एमएसएल" का वर्णन करता है। दुर्भाग्य से, यह कागज भी निराशाजनक है।

विशेष रूप से, निम्नलिखित बातें हैं।

सबसे पहले, एल्गोरिथ्म में कई गलतियाँ हैं और बहुत सारे अस्पष्टीकृत हैं। विशेष रूप से, यह पुनरावर्तन कॉल को विस्तृत नहीं करता है (मैं बस यह मानता हूं कि यह वर्तमान पारी और मुखौटा मूल्यों की गणना करने के लिए कुछ सूचक को घटाता है या कम करता है)। इसके अलावा, यह फ़ंक्शंस का उपयोग करता है dest_groupऔर dest_addressपरिभाषाएँ दिए बिना। मैं यह देखने में विफल हूं कि इनको कुशलता से कैसे लागू किया जाए (जो कि ओ (1) में है; कम से कम dest_addressतुच्छ नहीं है)।

अंतिम लेकिन कम से कम, एल्गोरिथ्म इनपुट सरणी के अंदर तत्वों के साथ सरणी सूचकांकों को स्वैप करके इन-प्लेस-नेस को प्राप्त करता है। यह स्पष्ट रूप से केवल संख्यात्मक सरणियों पर काम करता है। मुझे इसे स्ट्रिंग्स पर उपयोग करने की आवश्यकता है। बेशक, मैं सिर्फ मजबूत टाइपिंग कर सकता हूं और यह मानकर आगे बढ़ सकता हूं कि मेमोरी मेरे स्टोरेज इंडेक्स को बर्दाश्त कर लेगी जहां वह नहीं है। लेकिन यह केवल तब तक काम करता है जब तक मैं अपने तार को 32 बिट मेमोरी (32 बिट पूर्णांक मानकर) में निचोड़ सकता हूं। यह केवल 16 अक्षर है (आइए इस पल के लिए अनदेखा करें कि 16> लॉग (5,000,000))।

लेखकों में से एक अन्य पेपर में कोई सटीक विवरण नहीं दिया गया है, लेकिन यह एमएसएल के रनटाइम को सब-लीनियर के रूप में देता है जो गलत है।

पुनर्कथन करने के लिए : क्या काम के संदर्भ के कार्यान्वयन या कम से कम एक अच्छा छद्मकोड / एक काम करने वाली जगह के रेडिक्स के प्रकार का वर्णन करने की उम्मीद है जो डीएनए तार पर काम करता है?


65
यह एक उत्कृष्ट लिखित प्रश्न है।
जस्टिन टीटी

1
छोटे निश्चित लंबाई के तार कितने छोटे होते हैं?
21

1
@EvilTeach: मैंने उपयोग के मामलों को जोड़ दिया है।
कोनराड रुडोल्फ

2
@ स्टेफ़न: यह सब ठीक है और ठीक है। लेकिन नकल / कैश की कमी के मामले में मुझे बस देरी हो रही है। स्मृति के मामले में मैंने एक काल्पनिक सीमा पर प्रहार किया। यह केवल अप्राप्य है। डिस्क पर डेटा के कुछ हिस्सों को स्टोर करने के लिए उन सभी फैंसी तकनीकों को निश्चित रूप से वर्तमान क्विकॉर्ट समाधान की तुलना में धीमी है।
कोनराड रुडोल्फ

2
(cont ') dsimcha का समाधान, दूसरी ओर, निश्चित रूप से कुछ निविष्टियों के लिए quicksort की तुलना में तेज़ है। चालों की संख्या अधिक हो सकती है और स्थानीयता छोटी हो सकती है लेकिन वास्तविक दुनिया में, यह अभी भी अच्छा है। मैंने स्वैप की संख्या को कम करने के लिए समाधान को थोड़ा मोड़ दिया है जिसे मुझे प्रदर्शन करने की आवश्यकता है।
कोनराड रुडोल्फ

जवाबों:


61

खैर, यहाँ डीएनए के लिए एक एमएसडी मूलांक की तरह एक सरल कार्यान्वयन है। यह डी में लिखा है क्योंकि यह वह भाषा है जिसका मैं सबसे अधिक उपयोग करता हूं और इसलिए कम से कम गलतियाँ करने की संभावना है, लेकिन इसे आसानी से किसी अन्य भाषा में अनुवाद किया जा सकता है। यह जगह में है, लेकिन 2 * seq.lengthसरणी से गुजरना पड़ता है।

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

जाहिर है, यह डीएनए के लिए विशिष्ट है, जैसा कि सामान्य होने का विरोध है, लेकिन यह तेज होना चाहिए।

संपादित करें:

मैं उत्सुक हो गया कि क्या यह कोड वास्तव में काम करता है, इसलिए मैंने अपने बायोइनफॉरमैटिक्स कोड को चलाने के लिए प्रतीक्षा करते समय इसका परीक्षण / डिबग किया। अब उपरोक्त संस्करण वास्तव में परीक्षण किया गया है और काम करता है। 5 आधारों के 10 मिलियन अनुक्रमों के लिए, यह एक अनुकूलित आत्मनिरीक्षण से लगभग 3x तेज है।


9
यदि आप 2x पास दृष्टिकोण के साथ रह सकते हैं, तो यह मूलांक-एन तक फैला है: पास 1 = बस से गुजरें और गिनें कि एन अंकों में से प्रत्येक के कितने हैं। फिर यदि आप ऐरे को विभाजित कर रहे हैं तो यह बताता है कि प्रत्येक अंक कहाँ से शुरू होता है। दर्रा 2 सरणी में उपयुक्त स्थिति के लिए स्वैप करता है।
जेसन एस

(उदा = N = 4 के लिए, यदि 90000 A, 80000 G, 100 C, 100000 T हैं, तो एक सारणी को संचयी योगों के लिए आरम्भ किया जाता है = [0, 90000, 170000, 170100] जिसका उपयोग आपके APos के स्थान पर किया जाता है, CPos, आदि एक कर्सर के रूप में जहां प्रत्येक अंक के लिए अगले तत्व को स्वैप किया जाना चाहिए।)
जेसन एस

मुझे यकीन नहीं है कि बाइनरी प्रतिनिधित्व और इस स्ट्रिंग प्रतिनिधित्व के बीच क्या संबंध होने वाला है, इसके अलावा जरूरत से कम से कम 4 गुना अधिक मेमोरी का उपयोग करने के अलावा
स्टीफन एगर्मोंट

अब दृश्यों के साथ गति कैसी है? आप 5 की लंबाई के साथ पर्याप्त अलग नहीं हैं
Stephan Eggermont

4
यह रेडिक्स सॉर्ट अमेरिकी फ्लैग सॉर्ट का एक विशेष मामला लगता है - एक प्रसिद्ध इन-प्लेस रेडिक्स सॉर्ट संस्करण।
एडवर्ड KMETT

21

मैंने कभी भी एक इन-प्लेस रेडिक्स सॉर्ट नहीं देखा है, और रेडिक्स-सॉर्ट की प्रकृति से मुझे संदेह है कि यह जगह की तरह से बहुत तेज है जब तक कि अस्थायी सरणी मेमोरी में फिट नहीं हो जाती।

कारण:

छँटाई इनपुट सरणी पर एक रैखिक पढ़ता है, लेकिन सभी लिखते हैं लगभग यादृच्छिक होगा। एक निश्चित एन से ऊपर की ओर यह एक कैश मिस लिखता है। यह कैश मिस आपके एल्गोरिथ्म को धीमा कर देता है। यदि यह जगह में है या नहीं तो यह प्रभाव नहीं बदलेगा।

मुझे पता है कि यह आपके सवाल का सीधे जवाब नहीं देगा, लेकिन अगर छंटनी एक अड़चन है, तो आप एक प्रीप्रोसेसिंग चरण के रूप में एल्गोरिदम के पास एक नज़र रखना चाह सकते हैं (सॉफ्ट-हीप पर विकि-पेज आपको मिल सकता है)।

यह एक बहुत अच्छा कैश इलाके को बढ़ावा दे सकता है। एक पाठ्य-पुस्तक आउट-ऑफ-द-प्लेस रेडिक्स सॉर्ट तब बेहतर प्रदर्शन करेगी। लेखन अभी भी लगभग यादृच्छिक होगा लेकिन कम से कम वे स्मृति के उसी हिस्से के आसपास क्लस्टर करेंगे और जैसे कैश हिट अनुपात में वृद्धि करेंगे।

मुझे कोई पता नहीं है अगर यह व्यवहार में बाहर काम करता है।

Btw: यदि आप केवल डीएनए स्ट्रिंग्स के साथ काम कर रहे हैं: आप चार में दो बिट्स को संपीड़ित कर सकते हैं और अपने डेटा को काफी पैक कर सकते हैं। यह एक नेई प्रतिनिधित्व पर कारक चार से स्मृति की आवश्यकता को कम कर देगा। संबोधित करना अधिक जटिल हो जाता है, लेकिन आपके CPU के ALU के पास वैसे भी सभी कैश-मिस के दौरान बिताने के लिए बहुत समय है।


2
दो अच्छे अंक; पास छँटाई मेरे लिए एक नई अवधारणा है, मुझे इसके बारे में पढ़ना होगा। कैश मिस एक और विचार है जो मेरे सपनों का शिकार करता है। ;-) मुझे इस बारे में देखना होगा।
कोनराड रुडोल्फ

यह मेरे लिए भी नया है (एक-दो महीने में), लेकिन एक बार जब आपको यह अवधारणा मिल जाती है तो आपको प्रदर्शन में सुधार के अवसर दिखाई देने लगते हैं।
निल्स पिपेनब्रिनक

जब तक आपका मूलांक बहुत बड़ा न हो, लिखना लगभग यादृच्छिक से दूर होता है। उदाहरण के लिए, यह मानते हुए कि आप एक बार में एक वर्ण (एक मूलांक -4 क्रमांक) को लिखते हैं, सभी लिखते हैं कि एक पंक्ति में 4 रैखिक बाल्टियाँ होंगी। यह कैश और प्रीफैच फ्रेंडली दोनों है। बेशक, आप एक बड़े मूलांक का उपयोग करना चाह सकते हैं, और कुछ सूचक पर आप कैश और प्रीफ़ेट मित्रता और मूलांक के आकार के बीच एक ट्रेडऑफ़ मारते हैं। आप सॉफ्टवेयर के प्रीफेटिंग या "बाल्टी" के लिए अपने बाल्टियों के लिए एक खरोंच क्षेत्र का उपयोग करके "वास्तविक" बाल्टियों के साथ एक बड़े हिस्से की ओर ब्रेक-ईवन बिंदु को धक्का दे सकते हैं।
मधुमक्खी पालन

8

आप निश्चित रूप से बिट में अनुक्रम एन्कोडिंग द्वारा स्मृति आवश्यकताओं को छोड़ सकते हैं। आप क्रमांकन देख रहे हैं, तो लंबाई 2 के लिए, "ACGT" के साथ 16 राज्य या 4 बिट्स हैं। लंबाई 3 के लिए, यह 64 राज्य है, जिसे 6 बिट्स में एन्कोड किया जा सकता है। तो यह अनुक्रम में प्रत्येक अक्षर के लिए 2 बिट्स की तरह दिखता है, या 16 अक्षरों के लिए लगभग 32 बिट्स जैसा आपने कहा।

यदि वैध 'शब्दों' की संख्या को कम करने का एक तरीका है, तो आगे संपीड़न संभव हो सकता है।

तो लंबाई 3 के अनुक्रमों के लिए, कोई 64 बाल्टी बना सकता है, शायद uint32, या uint64 आकार। उन्हें शून्य पर प्रारंभ करें। 3 चार क्रमों की आपकी बहुत बड़ी सूची के माध्यम से Iterate करें, और उन्हें ऊपर बताएं। इसे एक सबस्क्रिप्ट के रूप में उपयोग करें, और उस बाल्टी को बढ़ाएँ।
इसे तब तक दोहराएं जब तक कि आपके सभी अनुक्रम संसाधित नहीं हो जाते।

इसके बाद, अपनी सूची पुन: बनाएँ।

64 बाल्टी के माध्यम से क्रमबद्ध करें, उस बाल्टी में पाई गई गणना के लिए, उस बाल्टी द्वारा दर्शाए गए अनुक्रम के कई उदाहरण उत्पन्न करें।
जब सभी बाल्टियों को पुनरावृत्त किया गया है, तो आपके पास अपना क्रमबद्ध सरणी है।

4 का एक क्रम, 2 बिट्स जोड़ता है, इसलिए 256 बाल्टी होगी। 5 का एक क्रम, 2 बिट्स जोड़ता है, इसलिए 1024 बाल्टी होगी।

कुछ बिंदु पर बाल्टियों की संख्या आपकी सीमा के करीब पहुंच जाएगी। यदि आप एक फ़ाइल से अनुक्रम पढ़ते हैं, तो उन्हें मेमोरी में रखने के बजाय, बाल्टी के लिए अधिक मेमोरी उपलब्ध होगी।

मुझे लगता है कि यह सीटू में सॉर्ट करने से ज्यादा तेज होगा क्योंकि बाल्टी आपके काम करने के सेट के भीतर फिट होने की संभावना है।

यहां एक हैक है जो तकनीक दिखाता है

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

जब आप हैश एह की तुलना क्यों कर सकते हैं?
wowest

1
बिलकुल सीधे। प्रदर्शन आमतौर पर किसी भी डीएनए प्रसंस्करण के साथ एक मुद्दा है।
एविलटाइच

6

यदि आपका डेटा सेट इतना बड़ा है, तो मुझे लगता है कि डिस्क-आधारित बफर दृष्टिकोण सबसे अच्छा होगा:

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

उदाहरण के लिए, यदि आपकी स्ट्रिंग थी, तो मैं एक बड़ी संख्या में बाल्टी में भी प्रयोग करूँगा:

GATTACA

पहला MSB कॉल GATT (256 कुल बकेट) के लिए बाल्टी लौटाएगा, इस तरह आप डिस्क आधारित बफर की कम शाखाएं बनाते हैं। इससे प्रदर्शन में सुधार हो सकता है या नहीं भी हो सकता है, इसलिए इसके साथ प्रयोग करें।


हम कुछ अनुप्रयोगों के लिए मेमोरी-मैप्ड फ़ाइलों का उपयोग करते हैं। हालांकि, सामान्य तौर पर हम इस धारणा के तहत काम करते हैं कि मशीन को स्पष्ट रूप से डिस्क बैकिंग की आवश्यकता नहीं है (बेशक, अभी भी स्वैपिंग होती है)। लेकिन हम पहले से ही स्वचालित डिस्क-समर्थित सरणियों के लिए एक तंत्र विकसित कर रहे हैं
कोनराड रूडोल्फ

6

मैं एक अंग पर बाहर जा रहा हूं और सुझाव देता हूं कि आप ढेर / ढेर लगाने के लिए स्विच करें । यह सुझाव कुछ मान्यताओं के साथ आता है:

  1. आप डेटा की रीडिंग को नियंत्रित करते हैं
  2. जैसे ही आप इसे शुरू कर रहे हैं 'आप' के रूप में जैसे ही आप सॉर्ट किए गए डेटा के साथ कुछ सार्थक कर सकते हैं।

हीप / हीप-सॉर्ट की सुंदरता यह है कि आप डेटा पढ़ते समय हीप का निर्माण कर सकते हैं, और आप उस क्षण के परिणाम प्राप्त करना शुरू कर सकते हैं, जिसे आपने बनाया है।

चलो वापस कदम। यदि आप इतने भाग्यशाली हैं कि आप डेटा को एसिंक्रोनस रूप से पढ़ सकते हैं (अर्थात, आप किसी तरह का रीड रिक्वेस्ट पोस्ट कर सकते हैं और कुछ डेटा तैयार होने पर अधिसूचित किया जा सकता है), और तब आप ढेर का निर्माण कर सकते हैं जब आप प्रतीक्षा कर रहे हों डिस्क से आने के लिए भी डेटा का अगला हिस्सा - अक्सर, यह दृष्टिकोण डेटा प्राप्त करने में लगने वाले समय के पीछे आपकी आधी छँटाई की अधिकांश लागत को दफन कर सकता है।

एक बार जब आप डेटा पढ़ लेते हैं, तो पहला तत्व पहले से ही उपलब्ध होता है। इस आधार पर कि आप डेटा कहां भेज रहे हैं, यह बहुत अच्छा हो सकता है। यदि आप इसे किसी अन्य अतुल्यकालिक पाठक, या कुछ समानांतर 'ईवेंट' मॉडल, या UI पर भेज रहे हैं, तो आप जाते ही विखंडू और विखंडू भेज सकते हैं।

उस ने कहा - यदि डेटा को कैसे पढ़ा जाता है, इस पर आपका कोई नियंत्रण नहीं है, और इसे सिंक्रोनस रूप से पढ़ा जाता है, और आपके पास सॉर्ट किए गए डेटा के लिए कोई उपयोग नहीं है, जब तक कि यह पूरी तरह से बाहर लिखा न हो - इस सब को अनदेखा करें। :(

विकिपीडिया लेख देखें:


1
अच्छा सुझाव। हालाँकि, मैंने पहले ही यह कोशिश कर ली है और मेरे विशेष मामले में ढेर बनाए रखने का ओवरहेड एक वेक्टर में डेटा को जमा करने और सभी डेटा आने के बाद एक बार छाँटने से बड़ा है।
कोनराड रूडोल्फ

5

" अतिरिक्त स्थान के साथ छंटनी " मूलांक आपकी समस्या को संबोधित करने वाला एक पेपर है।


होनहार लगता है, हालांकि समस्या वास्तव में पहले से ही हल हो गई है। फिर भी, यह मेरे संदर्भ पुस्तकालय में जाता है।
कोनराड रुडोल्फ

4

प्रदर्शन-वार आप अधिक सामान्य स्ट्रिंग-तुलना सॉर्टिंग एल्गोरिदम को देखना चाह सकते हैं।

वर्तमान में आप हर स्ट्रिंग के हर तत्व को छूते हैं, लेकिन आप बेहतर कर सकते हैं!

विशेष रूप से, एक फट प्रकार इस मामले के लिए बहुत अच्छा है। एक बोनस के रूप में, चूंकि बर्स्टोसर्ट कोशिशों पर आधारित है, यह डीएनए / आरएनए में उपयोग किए जाने वाले छोटे वर्णमाला आकारों के लिए हास्यास्पद रूप से अच्छी तरह से काम करता है, क्योंकि आपको किसी भी प्रकार के टर्नरी खोज नोड, हैश या अन्य त्रि नोड संपीड़न योजना का निर्माण करने की आवश्यकता नहीं है। त्रि-कार्यान्वयन। कोशिश आपके प्रत्यय-सरणी-जैसे अंतिम लक्ष्य के लिए भी उपयोगी हो सकती है।

बर्स्टॉर्ट का एक सामान्य सामान्य उद्देश्य कार्यान्वयन http://sourceforge.net/projects/burstsort/ पर स्रोत फोर्ज पर उपलब्ध है, लेकिन यह इन-प्लेस नहीं है।

तुलनात्मक उद्देश्यों के लिए, C- बर्स्टोर्ट कार्यान्वयन को http://www.cs.mu.oz.au/~rsinha/papers/SinhaRingZobel-2006.pdf के बेंचमार्क में शामिल किया गया है, जो कुछ विशिष्ट वर्कलोड की तुलना में क्विकॉर्ट्स और मूलांक की तुलना में 4-5 गुना तेज है।


मुझे निश्चित रूप से फट सॉर्ट को देखना होगा - हालांकि इस समय मैं यह नहीं देखता कि ट्राइ को कैसे बनाया जा सकता है। सामान्य प्रत्यय सरणियों में व्यावहारिक अनुप्रयोगों में बेहतर प्रदर्शन विशेषताओं के कारण जैव सूचना विज्ञान में सभी (लेकिन इस प्रकार, कोशिश करता है) प्रत्यय वृक्षों की जगह है।
कोनराड रुडोल्फ

4

आप डीआरएस द्वारा बड़े पैमाने पर जीनोम अनुक्रम प्रसंस्करण पर एक नज़र रखना चाहते हैं । कसारा और मोरिशिता।

स्ट्रिंग्स में चार न्यूक्लियोटाइड अक्षर A, C, G और T शामिल होते हैं, इन्हें बहुत तेजी से प्रसंस्करण के लिए विशेष रूप से इंटेगर में एन्कोड किया जा सकता है । पुस्तक में चर्चा किए गए कई एल्गोरिदम में मूलांक छंटाई है; आपको इस प्रश्न के स्वीकृत उत्तर को अनुकूलित करने और एक बड़ा प्रदर्शन सुधार देखने में सक्षम होना चाहिए।


इस पुस्तक में प्रस्तुत किया गया मूलांक क्रमबद्ध नहीं है, इसलिए यह इस उद्देश्य के लिए उपयोग करने योग्य नहीं है। स्ट्रिंग संघनन के लिए, मैं (निश्चित रूप से) पहले से ही ऐसा कर रहा हूं। मेरा (अधिक या कम) अंतिम समाधान (नीचे पोस्ट किया गया) यह नहीं दिखाता है क्योंकि पुस्तकालय मुझे उन्हें सामान्य तारों की तरह व्यवहार करने की अनुमति देता है - लेकिन RADIXइस्तेमाल किया गया मूल्य (और) निश्चित रूप से बड़े मूल्यों के लिए अनुकूलित किया जा सकता है।
कोनराड रुडोल्फ

3

आप एक तिकड़ी का उपयोग करने की कोशिश कर सकते हैं । डेटा को सॉर्ट करना केवल डेटासेट के माध्यम से पुनरावृत्ति करना और इसे सम्मिलित करना है; संरचना स्वाभाविक रूप से सॉर्ट की जाती है, और आप इसे बी-ट्री के समान समझ सकते हैं (तुलना करने के बजाय, आप हमेशा सूचक अप्रत्यक्ष उपयोग करते हैं)।

कैशिंग व्यवहार सभी आंतरिक नोड्स का पक्ष लेंगे, इसलिए आप शायद उस पर सुधार नहीं करेंगे; लेकिन आप अपने ट्राइ के फैक्टरिंग फैक्टर के साथ भी फिड कर सकते हैं (सुनिश्चित करें कि प्रत्येक नोड एक एकल कैश लाइन में फिट हो, एक ढेर के समान ट्राइ नोड्स आवंटित करें, एक सन्निहित सरणी के रूप में जो एक स्तर-क्रम ट्रैवर्सल का प्रतिनिधित्व करता है)। चूँकि कोशिशें भी डिजिटल संरचनाएं हैं (लंबाई के तत्वों के लिए ओ (के) डालें / लगाएं / हटाएं), आपके पास एक रेडिक्स सॉर्ट के लिए प्रतिस्पर्धी प्रदर्शन होना चाहिए।


तीनों को मेरे अनुभवहीन कार्यान्वयन के रूप में एक ही समस्या है: इसके लिए ओ (एन) अतिरिक्त मेमोरी की आवश्यकता होती है जो कि बस बहुत अधिक है।
कोनराड रुडोल्फ

3

मैं स्ट्रिंग्स के एक पैक-बिट प्रतिनिधित्व को फोड़ दूंगा । बर्स्टसॉर्ट का दावा है कि रेडिक्स की तुलना में बहुत बेहतर इलाके हैं, अतिरिक्त शास्त्रीय उपयोग को शास्त्रीय कोशिशों के स्थान पर नीचे रखने के साथ। मूल पेपर में माप है।


2

रेडिक्स-सॉर्ट कैश के प्रति सचेत नहीं है और बड़े सेटों के लिए सबसे तेज़ सॉर्ट एल्गोरिथ्म नहीं है। आप यहां देख सकते हैं:

आप संपीड़न का उपयोग कर सकते हैं और सॉर्ट सरणी में संग्रहीत करने से पहले अपने डीएनए के प्रत्येक अक्षर को 2 बिट्स में एन्कोड कर सकते हैं।


बिल: क्या आप बता सकते हैं कि C ++ द्वारा प्रदान किए गए फ़ंक्शन qsortपर इस फ़ंक्शन के क्या फायदे हैं std::sort? विशेष रूप से, उत्तरार्द्ध आधुनिक पुस्तकालयों में एक अत्यधिक परिष्कृत आत्मनिरीक्षण को लागू करता है और तुलना ऑपरेशन को रेखांकित करता है। मैं, ज्यादातर मामलों के लिए दावा है कि यह में O (n) का इस्तेमाल करता नहीं खरीदते हैं, क्योंकि यह (कम से कम बिना नहीं आत्मनिरीक्षण सामान्य स्थिति में उपलब्ध नहीं के एक डिग्री की आवश्यकता होगी एक बहुत भूमि के ऊपर के)।
कोनराड रुडोल्फ

मैं c ++ का उपयोग नहीं कर रहा हूं, लेकिन मेरे परीक्षण में इनलाइन QSORT stdlib में qsort की तुलना में 3 गुना तेज हो सकता है। Ti7qsort पूर्णांक (इनलाइन QSORT की तुलना में तेज़) के लिए सबसे तेज़ सॉर्ट है। आप छोटे निश्चित आकार के डेटा को सॉर्ट करने के लिए भी इसका उपयोग कर सकते हैं। आपको अपने डेटा के साथ परीक्षण करना चाहिए।
बिल

1

dsimcha का MSB मूलांक सॉर्ट अच्छा लगता है, लेकिन निल्स समस्या के दिल के करीब हो जाता है कि अवलोकन के साथ कि कैश लोकलिटी आपको बड़े समस्या आकार में मार रही है।

मैं एक बहुत ही सरल दृष्टिकोण सुझाता हूं:

  1. प्रायोगिक रूप से सबसे बड़े आकार का अनुमान लगाते हैं mजिसके लिए एक मूलांक क्रमांक कुशल होता है।
  2. mएक बार में तत्वों के ब्लॉक पढ़ें , मूलांक उन्हें सॉर्ट करें, और उन्हें लिख दें (यदि आपके पास पर्याप्त मेमोरी है, लेकिन अन्यथा फ़ाइल करने के लिए), जब तक आप अपने इनपुट को समाप्त नहीं करते।
  3. परिणामी सॉर्ट किए गए ब्लॉक को विलय करता है।

मर्जसॉर्ट सबसे अधिक कैश-फ्रेंडली सॉर्टिंग एल्गोरिथ्म है जिसके बारे में मुझे पता है: "सरणी ए या बी से अगले आइटम को पढ़ें, फिर आउटपुट बफर पर एक आइटम लिखें।" यह टेप ड्राइव पर कुशलता से चलता है । आइटम 2nको सॉर्ट करने के लिए स्थान की आवश्यकता होती है n, लेकिन मेरी शर्त यह है कि आप जिस बेहतर कैश लोकेशन को देखेंगे, वह आपको महत्वहीन बना देगा - और यदि आप एक गैर-इन-प्लेस रेडिक्स सॉर्ट का उपयोग कर रहे थे, तो आपको वैसे भी अतिरिक्त स्थान की आवश्यकता थी।

कृपया अंत में ध्यान दें कि विलय को पुनरावृत्ति के बिना लागू किया जा सकता है, और वास्तव में ऐसा करने से यह स्पष्ट रैखिक मेमोरी एक्सेस पैटर्न को स्पष्ट करता है।


1

ऐसा लगता है कि आपने समस्या को हल कर दिया है, लेकिन रिकॉर्ड के लिए, यह प्रतीत होता है कि एक कार्य-योग्य रेडिक्स सॉर्ट का एक संस्करण "अमेरिकन फ्लैग सॉर्ट" है। यह यहाँ वर्णित है: इंजीनियरिंग रेडिक्स सॉर्ट । सामान्य विचार प्रत्येक वर्ण पर 2 पास करना है - पहले यह गिनें कि आपके पास कितने हैं, इसलिए आप इनपुट ऐरे को डिब्बे में उपविभाजित कर सकते हैं। फिर सही बिन में प्रत्येक तत्व को स्वैप करते हुए, फिर से गुजरें। अब अगली वर्ण स्थिति पर प्रत्येक बिन को पुन: क्रमबद्ध करें।


वास्तव में, मैं जिस समाधान का उपयोग करता हूं वह फ्लैग सॉर्टिंग एल्गोरिदम से बहुत निकट से संबंधित है। मुझे नहीं पता कि क्या कोई प्रासंगिक अंतर है।
कोनराड रुडोल्फ

2
अमेरिकन फ्लैग सॉर्ट के बारे में कभी नहीं सुना, लेकिन जाहिर है कि मैंने क्या कोडित किया है: coliru.stacked-crooked.com/a/94eb75fbecc39066 यह वर्तमान में बेहतर प्रदर्शन कर std::sortरहा है, और मुझे यकीन है कि एक multidigit डिजिटाइज़र अभी भी तेजी से जा सकता है, लेकिन मेरे टेस्ट सूट में मेमोरी है समस्याओं (एल्गोरिथ्म नहीं, परीक्षण सूट ही)
मूइंग डक

@KonradRudolph: फ्लैग सॉर्ट और अन्य मूलांक के बीच बड़ा अंतर काउंटिंग पास है। आप सही हैं कि सभी मूलांक प्रकार बहुत निकट से संबंधित हैं, लेकिन मैं आपका ध्वज प्रकार नहीं मानूंगा।
मूइंग डक

@MooDDuck: बस वहाँ अपने नमूने से कुछ प्रेरणा ली - मैं अपने स्वयं के स्वतंत्र कार्यान्वयन में फंस गया, और आपकी मदद ने मुझे फिर से पटरी पर लाने में मदद की। धन्यवाद! एक संभव अनुकूलन - मुझे यह देखने के लिए यहां पर्याप्त नहीं मिला है कि क्या यह अभी तक सार्थक है: यदि आप जिस स्थिति में स्वैप कर रहे हैं उसमें तत्व पहले से ही है जहां यह होना चाहिए, आप इसे छोड़ सकते हैं और एक को आगे बढ़ा सकते हैं। नहीं है। यह पता लगाने के लिए अतिरिक्त तर्क की आवश्यकता होगी, और अतिरिक्त अतिरिक्त भंडारण के रूप में अच्छी तरह से, लेकिन चूंकि स्वैप तुलना करने के लिए महंगा हैं, इसलिए यह करने योग्य हो सकता है।
500 - आंतरिक सर्वर त्रुटि

1

सबसे पहले, अपनी समस्या के कोडिंग के बारे में सोचें। स्ट्रिंग्स से छुटकारा पाएं, उन्हें बाइनरी प्रतिनिधित्व द्वारा प्रतिस्थापित करें। लंबाई + एन्कोडिंग इंगित करने के लिए पहले बाइट का उपयोग करें। वैकल्पिक रूप से, चार-बाइट सीमा पर एक निश्चित लंबाई प्रतिनिधित्व का उपयोग करें। फिर मूलांक छांटना बहुत आसान हो जाता है। एक मूलांक सॉर्ट के लिए, सबसे महत्वपूर्ण बात यह है कि आंतरिक लूप के गर्म स्थान पर अपवाद हैंडलिंग नहीं है।

ठीक है, मैंने 4-नारी समस्या के बारे में थोड़ा और सोचा। आपको जूडी के पेड़ जैसा हल चाहिए लिए । अगला समाधान चर लंबाई के तारों को संभाल सकता है; निश्चित लंबाई के लिए बस लंबाई बिट्स को हटा दें, जो वास्तव में इसे आसान बनाता है।

16 बिंदुओं के ब्लॉक आवंटित करें। कम से कम महत्वपूर्ण बिंदुओं का पुन: उपयोग किया जा सकता है, क्योंकि आपके ब्लॉक हमेशा संरेखित होंगे। आप इसके लिए एक विशेष भंडारण आबंटक चाहते हैं (छोटे स्टोरेज में बड़े स्टोरेज को तोड़कर)। विभिन्न प्रकार के कई ब्लॉक हैं:

  • चर लंबाई लंबाई के 7 लंबाई बिट्स के साथ एन्कोडिंग। जैसे ही वे भरते हैं, आप उन्हें प्रतिस्थापित करते हैं:
  • स्थिति अगले दो वर्णों को कूटबद्ध करती है, आपके पास अगले खंड के 16 बिंदु हैं, जिनके साथ समाप्त होता है:
  • किसी स्ट्रिंग के अंतिम तीन अक्षरों का बिटमैप एन्कोडिंग।

प्रत्येक प्रकार के ब्लॉक के लिए, आपको एलएसबी में अलग-अलग जानकारी संग्रहीत करने की आवश्यकता है। जैसा कि आपके पास परिवर्तनीय लंबाई के तार हैं, आपको अंत-स्ट्रिंग को भी संग्रहीत करने की आवश्यकता है, और अंतिम प्रकार के ब्लॉक का उपयोग केवल सबसे लंबे तार के लिए किया जा सकता है। 7 लंबाई की बिट्स को कम से कम प्रतिस्थापित किया जाना चाहिए क्योंकि आप संरचना में गहराई से आते हैं।

यह आपको क्रमबद्ध रूप से तेज और बहुत ही स्मृति कुशल छंटाई के भंडारण प्रदान करता है। यह एक ट्राइ की तरह कुछ व्यवहार करेगा । इस काम को करने के लिए, पर्याप्त यूनिट परीक्षण सुनिश्चित करें। आप सभी ब्लॉक संक्रमणों का कवरेज चाहते हैं। आप केवल दूसरे प्रकार के ब्लॉक से शुरुआत करना चाहते हैं।

और भी अधिक प्रदर्शन के लिए, आप विभिन्न प्रकार के ब्लॉक और बड़े आकार के ब्लॉक जोड़ना चाह सकते हैं। यदि ब्लॉक हमेशा समान आकार और बड़े होते हैं, तो आप पॉइंटर्स के लिए कम बिट्स का उपयोग कर सकते हैं। 16 पॉइंटर्स के ब्लॉक साइज के साथ, आपके पास पहले से ही 32-बिट एड्रेस स्पेस में एक बाइट फ्री है। दिलचस्प ब्लॉक प्रकारों के लिए जूडी ट्री प्रलेखन पर एक नज़र डालें। मूल रूप से, आप एक स्थान (और क्रम) व्यापार बंद के लिए कोड और इंजीनियरिंग समय जोड़ते हैं

आप संभवत: पहले चार वर्णों के लिए 256 विस्तृत प्रत्यक्ष मूलांक के साथ शुरुआत करना चाहते हैं। यह एक अच्छा स्थान / समय व्यापार प्रदान करता है। इस कार्यान्वयन में, आपको एक साधारण ट्राइ की तुलना में बहुत कम मेमोरी ओवरहेड मिलता है; यह लगभग तीन गुना छोटा है (मैंने मापा नहीं है)। O (n) कोई समस्या नहीं है यदि स्थिरांक काफी कम है, जैसा कि आपने देखा जब O (n log n) क्विकॉर्ट के साथ तुलना की गई।

क्या आप युगल को संभालने में रुचि रखते हैं? छोटे दृश्यों के साथ, होने जा रहे हैं। गिनती को संभालने के लिए ब्लॉक को अपनाना मुश्किल है, लेकिन यह बहुत ही अंतरिक्ष-कुशल हो सकता है।


अगर मैं एक बिट-पैक प्रतिनिधित्व का उपयोग करता हूं तो मुझे नहीं लगता कि मेरे मामले में मूलांक कैसे आसान हो जाता है। वैसे, मैं जिस फ्रेमवर्क का उपयोग करता हूं, वह वास्तव में बिट-पैक प्रतिनिधित्व का उपयोग करने की संभावना प्रदान करता है लेकिन यह इंटरफ़ेस के उपयोगकर्ता के रूप में मेरे लिए पूरी तरह से पारदर्शी है।
कोनराड रुडोल्फ

जब आप अपनी स्टॉपवॉच को नहीं देखते हैं :)
स्टीफन एगरमॉन्ट

मैं निश्चित रूप से जूडी पेड़ों पर एक नजर डालूंगा। वेनिला वास्तव में मेज पर ज्यादा लाने की कोशिश नहीं करता है क्योंकि वे मूल रूप से तत्वों पर कम पास के साथ एक सामान्य एमएसडी मूलांक की तरह व्यवहार करते हैं लेकिन अतिरिक्त भंडारण की आवश्यकता होती है।
कोनराड रुडोल्फ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.