เรียงลำดับ Radix ในสถานที่


200

นี่คือข้อความยาว กรุณาทนกับฉัน ต้มคำถามก็คือ: มีอัลกอริทึมการเรียงลำดับ Radix แบบทำงานได้หรือไม่?


เบื้องต้น

ฉันมีสตริงความยาวคงที่ขนาดเล็กจำนวนมากที่ใช้เฉพาะตัวอักษร“ A”,“ C”,“ G” และ“ T” (ใช่คุณเดาได้แล้วว่า: DNA ) ที่ฉันต้องการเรียงลำดับ

ในขณะที่ผมใช้std::sortซึ่งใช้introsortในการใช้งานร่วมกันทั้งหมดของSTL ใช้งานได้ค่อนข้างดี อย่างไรก็ตามฉันเชื่อมั่นว่าการเรียงตัวของ Radix ตรงกับปัญหาที่ฉันตั้งไว้อย่างสมบูรณ์แบบและควรทำงานได้ดีขึ้นมากในทางปฏิบัติ

รายละเอียด

ฉันได้ทดสอบสมมติฐานนี้ด้วยการใช้งานที่ไร้เดียงสามากและสำหรับอินพุตที่ค่อนข้างเล็ก (ตามลำดับ 10,000) นี่เป็นจริง (ดีอย่างน้อยก็เร็วกว่าสองเท่า) อย่างไรก็ตามรันไทม์จะลดลงอย่างมากเมื่อขนาดของปัญหาใหญ่ขึ้น ( N > 5,000,000)

เหตุผลชัดเจน: การเรียง radix ต้องคัดลอกข้อมูลทั้งหมด (มากกว่าหนึ่งครั้งในการใช้งานไร้เดียงสาของฉันจริง) ซึ่งหมายความว่าฉันได้ใส่ ~ 4 GiB ลงในหน่วยความจำหลักซึ่งฆ่าได้อย่างชัดเจน แม้ว่ามันจะไม่เป็นเช่นนั้นก็ตามฉันก็ไม่สามารถใช้หน่วยความจำนี้ได้มากขนาดของปัญหาจะใหญ่ขึ้น

ใช้เคส

อัลกอริทึมนี้ควรทำงานกับความยาวสตริงใด ๆ ระหว่าง 2 ถึง 100 สำหรับ DNA และ DNA5 (ซึ่งอนุญาตให้ใช้อักขระตัวแทนเพิ่มเติม“ N”) หรือแม้แต่ DNA ที่มีรหัสความคลุมเครือIUPAC (ทำให้มีค่า 16 ค่าที่แตกต่างกัน) อย่างไรก็ตามฉันตระหนักว่ากรณีเหล่านี้ไม่สามารถครอบคลุมได้ดังนั้นฉันจึงมีความสุขกับการปรับปรุงความเร็วที่ฉันได้รับ รหัสสามารถตัดสินใจแบบไดนามิกอัลกอริทึมที่จะส่งไป

วิจัย

น่าเสียดายที่บทความ Wikipedia เกี่ยวกับการเรียงตัวของ Radixไม่มีประโยชน์ ส่วนเกี่ยวกับตัวแปรในสถานที่เป็นขยะสมบูรณ์ ส่วนNIST-DADS ในการจัดเรียงแบบ Radixอยู่ถัดจากไม่มีอยู่ มีกระดาษที่มีแนวโน้มที่เรียกว่าการเรียงลำดับ Radix แบบปรับตัวได้อย่างมีประสิทธิภาพซึ่งอธิบายอัลกอริทึม“ MSL” น่าเสียดายที่กระดาษนี้ก็น่าผิดหวังเช่นกัน

โดยเฉพาะอย่างยิ่งมีสิ่งต่าง ๆ ดังต่อไปนี้

ขั้นแรกอัลกอริทึมมีข้อผิดพลาดหลายประการ โดยเฉพาะอย่างยิ่งมันไม่ได้รายละเอียดการเรียกซ้ำ (ฉันเพียงแค่คิดว่ามันจะเพิ่มหรือลดตัวชี้บางอย่างเพื่อคำนวณค่ากะและหน้ากากในปัจจุบัน) นอกจากนี้ยังใช้ฟังก์ชั่นdest_groupและdest_addressไม่ได้ให้คำจำกัดความ ฉันล้มเหลวในการดูวิธีการใช้สิ่งเหล่านี้อย่างมีประสิทธิภาพ (นั่นคือใน O (1); อย่างน้อยdest_addressก็ไม่สำคัญเลย)

ขั้นตอนสุดท้าย แต่ไม่ท้ายสุดอัลกอริทึมจะได้รับการตอบสนองแบบ in-place-ness โดยการสลับดัชนีอาเรย์กับองค์ประกอบภายในอาเรย์อินพุต เห็นได้ชัดว่าใช้งานได้กับอาร์เรย์ตัวเลขเท่านั้น ฉันต้องใช้มันกับสตริง แน่นอนฉันสามารถสกรูการพิมพ์ที่แข็งแกร่งและไปข้างหน้าสมมติว่าหน่วยความจำจะทนต่อการจัดเก็บดัชนีที่มันไม่ได้เป็นของฉัน แต่วิธีนี้จะใช้ได้ตราบใดที่ฉันสามารถบีบสตริงของฉันลงในหน่วยความจำ 32 บิต (สมมติว่าเป็นจำนวนเต็ม 32 บิต) นั่นเป็นเพียง 16 ตัวอักษร (อย่าเพิกเฉยกับช่วงเวลานั้น> 16 บันทึก (5,000,000))

กระดาษอื่นโดยผู้เขียนคนหนึ่งไม่ได้ให้คำอธิบายที่ถูกต้องเลย แต่มันก็ให้ MSL ของ runtime เป็น sub-linear ซึ่งเรียบผิด

ในการสรุป : มีความหวังในการค้นหาการนำการอ้างอิงไปใช้งานหรืออย่างน้อยรหัสเทียม / คำอธิบายที่ดีของการเรียงลำดับแบบฝังในที่ทำงานที่ทำงานบนสาย DNA หรือไม่?


65
นั่นคือคำถามหนึ่งที่เขียนอย่างยอดเยี่ยม
JustinT

1
สตริงความยาวคงที่เล็กแค่ไหน
EvilTeach

1
@EvilTeach: ฉันได้เพิ่มเคสการใช้งานแล้ว
Konrad Rudolph

2
@ สเตฟาน: ทั้งหมดนี้ดีและดี แต่ในกรณีของการคัดลอก / แคชฉันเพิ่งได้รับล่าช้า ในกรณีของหน่วยความจำฉันถึงขีด จำกัด phyical นี้ไม่สามารถเจรจาต่อรองเพียง เทคนิคที่แปลกประหลาดเหล่านี้ในการจัดเก็บบางส่วนของข้อมูลบนดิสก์นั้นช้ากว่าโซลูชั่น Quicksort ปัจจุบันอย่างแน่นอน
Konrad Rudolph

2
(ต่อ ') วิธีแก้ปัญหาของ dsimcha ในทางตรงกันข้ามจะเร็วกว่าการเรียงแบบเร็วสำหรับอินพุตบางตัว จำนวนการเคลื่อนไหวอาจสูงและพื้นที่แคชเล็ก แต่ในโลกแห่งความเป็นจริงมันก็ยังดี ฉันได้ปรับแต่งโซลูชันเล็กน้อยเพื่อลดจำนวนการแลกเปลี่ยนที่ฉันต้องดำเนินการ
Konrad Rudolph

คำตอบ:


61

ต่อไปนี้เป็นวิธีการที่เรียบง่ายสำหรับการจัดเรียงของ MSD radix สำหรับ DNA มันเขียนด้วยภาษา D เพราะเป็นภาษาที่ฉันใช้บ่อยที่สุดและมีโอกาสน้อยที่จะทำผิดพลาด แต่ก็สามารถแปลเป็นภาษาอื่นได้อย่างง่ายดาย มันอยู่ในสถานที่ แต่ต้อง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);
   }
}

เห็นได้ชัดว่านี่เป็นลักษณะเฉพาะของ DNA เมื่อเทียบกับคนทั่วไป แต่ควรเร็ว

แก้ไข:

ฉันอยากรู้ว่ารหัสนี้ใช้งานได้จริงหรือไม่ดังนั้นฉันจึงทดสอบ / ตรวจแก้จุดบกพร่องในขณะที่รอรหัสชีวสารสนเทศของตัวเองให้ทำงาน เวอร์ชันด้านบนตอนนี้ผ่านการทดสอบและใช้งานได้จริง สำหรับ 10 ล้านลำดับของ 5 เบสแต่ละอันมันเร็วกว่า 3x Introsort ที่ดีที่สุด


9
หากคุณสามารถใช้ชีวิตด้วยวิธี 2x pass สิ่งนี้จะรวมถึง radix-N: pass 1 = แค่ผ่านไปแล้วนับจำนวน N หลักแต่ละตัว จากนั้นถ้าคุณแบ่งพาร์ติชันอาร์เรย์มันจะบอกคุณว่าแต่ละหลักเริ่มต้นที่ รหัสผ่าน 2 จะสลับไปยังตำแหน่งที่เหมาะสมในอาร์เรย์
เจสัน S

(เช่นสำหรับ N = 4 หากมี 90000 A, 80000 G, 100 C, 100000 T จากนั้นสร้างอาร์เรย์ที่เริ่มต้นไปที่ผลรวมสะสม = [0, 90000, 170000, 170100] ซึ่งใช้แทน APos ของคุณ CPOs ฯลฯ เป็นเคอร์เซอร์สำหรับที่องค์ประกอบต่อไปสำหรับแต่ละหลักควรจะเปลี่ยนไป).
เจสัน S

ฉันไม่แน่ใจว่าสิ่งที่ความสัมพันธ์ระหว่างตัวแทนไบนารีและแสดงสตริงนี้เป็นไปได้นอกเหนือจากการใช้อย่างน้อย 4 ครั้งเป็นหน่วยความจำมากที่สุดเท่าที่จำเป็น
สเตฟาน Eggermont

ความเร็วเป็นอย่างไรกับลำดับที่ยาวขึ้น? คุณมีจำนวนไม่มากพอที่มีความยาว 5
เตฟาน Eggermont

4
การเรียงลำดับ radix นี้ดูเหมือนว่าจะเป็นกรณีพิเศษของการเรียงลำดับ American Flag ซึ่งเป็นตัวแปรการเรียงลำดับ radix ที่รู้จักกันดี
Edward KMETT

21

ฉันไม่เคยเห็นประเภท radix แบบแทนที่และจากลักษณะของ radix-sort ฉันสงสัยว่ามันเร็วกว่าการเรียงแบบนอกสถานที่ตราบใดที่อาร์เรย์ชั่วคราวเข้ากับหน่วยความจำ

เหตุผล:

การเรียงลำดับจะอ่านเชิงเส้นบนอาเรย์อินพุต แต่การเขียนทั้งหมดจะเกือบจะสุ่ม จาก N ขึ้นไปสิ่งนี้ทำให้เดือดถึงการพลาดแคชต่อการเขียน แคชนี้พลาดคือสิ่งที่ทำให้อัลกอริทึมของคุณช้าลง หากอยู่ในสถานที่หรือไม่จะไม่เปลี่ยนผลกระทบนี้

ฉันรู้ว่านี่จะไม่ตอบคำถามของคุณโดยตรง แต่หากการเรียงลำดับเป็นคอขวดคุณอาจต้องการดูอัลกอริธึมการเรียงลำดับใกล้เคียงเป็นขั้นตอนก่อนการประมวลผล (หน้า wiki บน soft-heap อาจช่วยให้คุณเริ่มต้น)

นั่นอาจทำให้เพิ่มตำแหน่งแคชได้ดีมาก การเรียงลำดับของข้อความนอกสถานที่จะทำงานได้ดีขึ้น การเขียนจะยังคงเกือบจะสุ่ม แต่อย่างน้อยก็จะจัดกลุ่มรอบ ๆ หน่วยความจำเดียวกันและเพิ่มอัตราส่วนการเข้าถึงแคช

ฉันไม่รู้ว่ามันใช้ได้จริงในทางปฏิบัติ

Btw: หากคุณกำลังจัดการกับสาย DNA เท่านั้น: คุณสามารถบีบอัดถ่านเป็นสองบิตและแพ็คข้อมูลของคุณได้ค่อนข้างมาก สิ่งนี้จะลดความต้องการหน่วยความจำลงโดยใช้ตัวคูณสี่แทนการแสดงแบบ naiive การกำหนดที่อยู่จะซับซ้อนมากขึ้น แต่ ALU ของ CPU ของคุณมีเวลามากมายที่จะใช้ในช่วงที่แคชหายไป


2
สองคะแนนที่ดี; การเรียงลำดับใกล้เป็นแนวคิดใหม่สำหรับฉันฉันจะต้องอ่านเกี่ยวกับเรื่องนี้ การพลาดแคชคือการพิจารณาอีกอย่างที่หลอกหลอนความฝันของฉัน ;-) ฉันจะต้องดูเกี่ยวกับเรื่องนี้
Konrad Rudolph

มันใหม่สำหรับฉันเช่นกัน (สองสามเดือน) แต่เมื่อคุณมีแนวคิดคุณจะเริ่มเห็นโอกาสในการปรับปรุงประสิทธิภาพ
Nils Pipenbrinck

การเขียนอยู่ไกลจากการสุ่มเกือบเว้นแต่ว่าเลขฐานของคุณจะใหญ่มาก ตัวอย่างเช่นสมมติว่าคุณเรียงลำดับอักขระหนึ่งตัวต่อครั้ง (การจัดเรียง radix-4) การเขียนทั้งหมดจะเป็นหนึ่งใน 4 ถังที่เพิ่มขึ้นเชิงเส้น นี่เป็นทั้งแคชและการดึงข้อมูลล่วงหน้าได้ง่าย แน่นอนคุณอาจต้องการใช้ radix ที่มีขนาดใหญ่ขึ้นและที่ตัวชี้บางอย่างคุณจะเห็นการแลกเปลี่ยนระหว่างแคชกับความง่ายในการดึงข้อมูลและขนาดของ radix คุณสามารถผลักจุดคุ้มทุนไปสู่ ​​radices ที่มีขนาดใหญ่ขึ้นโดยใช้การดึงซอฟต์แวร์ล่วงหน้าหรือพื้นที่เริ่มต้นสำหรับที่เก็บข้อมูลของคุณ
BeeOnRope

8

คุณสามารถลดความต้องการหน่วยความจำลงได้อย่างแน่นอนโดยการเข้ารหัสลำดับเป็นบิต คุณกำลังดูวิธีเรียงสับเปลี่ยนดังนั้นสำหรับความยาว 2 ด้วย "ACGT" ที่ 16 สถานะหรือ 4 บิต สำหรับความยาว 3 นั่นคือ 64 สถานะซึ่งสามารถเข้ารหัสใน 6 บิต ดังนั้นดูเหมือนว่า 2 บิตสำหรับตัวอักษรแต่ละตัวตามลำดับหรือประมาณ 32 บิตสำหรับอักขระ 16 ตัวตามที่คุณพูด

หากมีวิธีการลดจำนวนคำที่ถูกต้องอาจมีการบีบอัดเพิ่มเติมได้

ดังนั้นสำหรับลำดับความยาว 3 เราสามารถสร้าง 64 buckets ขนาดบางที uint32 หรือ uint64 เริ่มต้นให้เป็นศูนย์ ทำซ้ำรายการลำดับถ่าน 3 ตัวที่มีขนาดใหญ่มากของคุณแล้วเข้ารหัสตามข้างต้น ใช้สิ่งนี้เป็นตัวห้อยและเพิ่มที่เก็บข้อมูล
ทำซ้ำสิ่งนี้จนกว่าลำดับทั้งหมดของคุณจะได้รับการประมวลผล

ถัดไปสร้างรายการของคุณใหม่

วนซ้ำผ่าน 64 buckets ตามลำดับสำหรับจำนวนที่พบในที่ฝากข้อมูลนั้นสร้างอินสแตนซ์จำนวนมากของลำดับที่แสดงโดยที่ฝากข้อมูลนั้น
เมื่อถังทั้งหมดได้รับการทำซ้ำคุณจะมีแถวเรียงของคุณ

ลำดับ 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;
}

เปรียบเทียบเมื่อคุณสามารถแฮชทำไม
สุดยอด

1
ด่าตรง ประสิทธิภาพโดยทั่วไปเป็นปัญหากับการประมวลผล DNA ใด ๆ
EvilTeach

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 ถัง) ซึ่งเป็นวิธีที่คุณสร้างสาขาย่อยน้อยลงของบัฟเฟอร์จากดิสก์ สิ่งนี้อาจหรือไม่อาจปรับปรุงประสิทธิภาพดังนั้นลองทดสอบดู


เราใช้ไฟล์ที่แมปหน่วยความจำสำหรับบางแอปพลิเคชัน อย่างไรก็ตามโดยทั่วไปเราทำงานภายใต้สมมติฐานว่าเครื่องมี RAM เพียงพอที่จะไม่ต้องการการสำรองดิสก์อย่างชัดเจน (แน่นอนว่าการแลกเปลี่ยนยังคงเกิดขึ้น) แต่เรากำลังพัฒนากลไกสำหรับอาร์เรย์ที่ดิสก์สำรองไว้โดยอัตโนมัติ
Konrad Rudolph

6

ฉันจะไปบนกิ่งก้านและขอแนะนำให้คุณสลับไปยังกอง / heapsortการดำเนินงาน ข้อเสนอแนะนี้มาพร้อมกับสมมติฐานบางประการ:

  1. คุณสามารถควบคุมการอ่านข้อมูล
  2. คุณสามารถทำสิ่งที่มีความหมายกับข้อมูลที่เรียงลำดับทันทีที่คุณ 'เริ่ม' รับมันเรียง

ความสวยงามของ heap / heap-sort คือคุณสามารถสร้าง heap ในขณะที่คุณอ่านข้อมูลและคุณสามารถเริ่มรับผลลัพธ์ทันทีที่คุณสร้าง heap

ลองย้อนกลับไป หากคุณโชคดีที่คุณสามารถอ่านข้อมูลแบบอะซิงโครนัสได้ (นั่นคือคุณสามารถโพสต์คำขอการอ่านบางประเภทและได้รับการแจ้งเตือนเมื่อมีข้อมูลพร้อม) จากนั้นคุณสามารถสร้างกองข้อมูลในขณะที่รอ อันถัดไปของข้อมูลที่จะมา - แม้จากดิสก์ บ่อยครั้งที่วิธีการนี้สามารถฝังค่าใช้จ่ายส่วนใหญ่ครึ่งหนึ่งของการเรียงลำดับของคุณที่อยู่เบื้องหลังเวลาที่ใช้ในการรับข้อมูล

เมื่อคุณอ่านข้อมูลแล้วองค์ประกอบแรกจะพร้อมใช้งานแล้ว ขึ้นอยู่กับว่าคุณกำลังส่งข้อมูลไปที่ใดมันยอดเยี่ยมมาก หากคุณกำลังส่งไปยังเครื่องอ่านอะซิงโครนัสอื่นหรือโมเดล 'เหตุการณ์' แบบขนานหรือ UI บางอย่างคุณสามารถส่งชิ้นส่วนและชิ้นตามที่คุณไป

ที่กล่าว - หากคุณไม่สามารถควบคุมวิธีการอ่านข้อมูลและอ่านแบบซิงโครนัสและคุณไม่ได้ใช้สำหรับข้อมูลที่เรียงลำดับจนกว่าจะถูกเขียนออกมาทั้งหมด - ไม่สนใจสิ่งนี้ทั้งหมด :(

ดูบทความ Wikipedia:


1
คำแนะนำที่ดี อย่างไรก็ตามฉันได้ลองแล้วและในกรณีของฉันโดยเฉพาะค่าใช้จ่ายในการดูแลรักษาฮีปนั้นมากกว่าการรวบรวมข้อมูลในเวกเตอร์และเรียงลำดับเมื่อข้อมูลทั้งหมดมาถึง
Konrad Rudolph

5

" การเรียงลำดับ Radix โดยไม่มีพื้นที่เพิ่มเติม " เป็นกระดาษที่จัดการปัญหาของคุณ


ดูมีแนวโน้มแม้ว่าปัญหาได้รับการแก้ไขจริงแล้ว ยังคงนี้จะเข้าไปในห้องสมุดอ้างอิงของฉัน
Konrad Rudolph

4

ประสิทธิภาพที่ชาญฉลาดคุณอาจต้องการดูอัลกอริธึมการเรียงลำดับการเปรียบเทียบสตริงทั่วไปเพิ่มเติม

ขณะนี้คุณไขลานการสัมผัสองค์ประกอบทุกส่วนของสตริง แต่คุณสามารถทำได้ดีกว่า!

โดยเฉพาะอย่างยิ่งการเรียงต่อเนื่องเป็นแบบที่ดีมากสำหรับกรณีนี้ เป็นโบนัสเนื่องจาก burstsort ขึ้นอยู่กับความพยายามมันทำงานได้ดีสำหรับตัวอักษรขนาดเล็กที่ใช้ใน DNA / RNA เนื่องจากคุณไม่จำเป็นต้องสร้างโหนดการค้นหาแบบไตรภาค, แฮชหรือโครงร่างการบีบอัดโหนด trie อื่น ๆ การใช้ Trie ความพยายามนี้อาจมีประโยชน์สำหรับเป้าหมายสุดท้ายเหมือนอาร์เรย์ต่อท้ายของคุณเช่นกัน

การใช้งานวัตถุประสงค์ทั่วไปที่เหมาะสมของ burstsort นั้นมีอยู่ที่แหล่งปลอมที่http://sourceforge.net/projects/burstsort/ - แต่มันไม่ได้อยู่ในสถานที่

เพื่อวัตถุประสงค์ในการเปรียบเทียบการใช้งาน C-burstsort ครอบคลุมที่http://www.cs.mu.oz.au/~rsinha/papers/SinhaRingZobel-2006.pdfเกณฑ์มาตรฐานเร็วขึ้น 4-5 เท่าจาก quicksort และ radix สำหรับปริมาณงานทั่วไปบางอย่าง


แน่นอนฉันจะต้องดูการเรียงลำดับการระเบิด - แม้ว่าในขณะนี้ฉันไม่เห็นวิธีการสร้างคู่ชีวิตในสถานที่ โดยทั่วไปอาร์เรย์ต่อท้ายจะมีทั้งหมด แต่แทนที่ต้นไม้ต่อท้าย (และพยายาม) ในชีวสารสนเทศศาสตร์เนื่องจากมีประสิทธิภาพที่เหนือกว่าในการใช้งานจริง
Konrad Rudolph

4

คุณจะต้องดูที่การประมวลผลลำดับจีโนมขนาดใหญ่โดย Drs Kasahara และ Morishita

Strings ประกอบด้วยสี่ตัวอักษรเบส A, C, G, และ T สามารถเข้ารหัสพิเศษเข้าไปในจำนวนเต็มสำหรับมากประมวลผลได้เร็วขึ้น การเรียงลำดับ Radix เป็นหนึ่งในอัลกอริทึมมากมายที่กล่าวถึงในหนังสือ; คุณควรจะสามารถปรับคำตอบที่ได้รับการยอมรับสำหรับคำถามนี้และดูการปรับปรุงประสิทธิภาพครั้งใหญ่


การเรียงลำดับ radix ที่นำเสนอในหนังสือเล่มนี้ไม่ได้อยู่ในสถานที่ดังนั้นจึงไม่สามารถใช้งานได้สำหรับวัตถุประสงค์นี้ สำหรับการบีบอัดสตริงฉัน (แน่นอน) ทำสิ่งนี้อยู่แล้ว โซลูชันสุดท้ายของฉัน (มากหรือน้อย) (โพสต์ด้านล่าง) ไม่แสดงสิ่งนี้เพราะห้องสมุดอนุญาตให้ฉันปฏิบัติต่อพวกเขาเหมือนสตริงปกติ - แต่แน่นอนว่าRADIXค่าที่ใช้สามารถ (และเป็น) จะถูกปรับให้มีค่ามากขึ้น
Konrad Rudolph

3

คุณอาจลองใช้Trie การเรียงลำดับข้อมูลเป็นเพียงการวนซ้ำผ่านชุดข้อมูลและใส่เข้าไป โครงสร้างถูกจัดเรียงตามธรรมชาติและคุณสามารถคิดว่ามันคล้ายกับ B-Tree (ยกเว้นแทนที่จะทำการเปรียบเทียบคุณจะใช้ตัวชี้ทิศทาง)

พฤติกรรมการแคชจะสนับสนุนโหนดภายในทั้งหมดดังนั้นคุณอาจจะไม่ดีขึ้น แต่คุณสามารถเล่นซอกับปัจจัยการแยกของ trie ของคุณได้เช่นกัน (ให้แน่ใจว่าทุก ๆ โหนดพอดีกับแคชบรรทัดเดียวจัดสรรโหนด trie ที่คล้ายกับฮีปเป็นอาเรย์ที่ต่อเนื่องกัน เนื่องจากความพยายามเป็นโครงสร้างดิจิทัล (O (k) insert / find / delete สำหรับองค์ประกอบที่มีความยาว k) คุณจึงควรมีประสิทธิภาพในการแข่งขันสำหรับการจัดเรียง Radix


Trie มีปัญหาเช่นเดียวกับการใช้งานแบบไร้เดียงสาของฉัน: ต้องใช้ O (n) หน่วยความจำเพิ่มเติมซึ่งมากเกินไป
Konrad Rudolph

3

ฉันจะระเบิดการเป็นตัวแทนบิตบรรจุ Burstsort นั้นอ้างว่ามีพื้นที่ใกล้เคียงกว่า Radix แปลก ๆ ทำให้การใช้พื้นที่พิเศษลดลงด้วยการพยายามระเบิดแทนการลองแบบดั้งเดิม กระดาษต้นฉบับมีการวัด


2

Radix-Sort ไม่แคชที่ใส่ใจและไม่ใช่อัลกอริทึมการเรียงลำดับที่เร็วที่สุดสำหรับชุดใหญ่ คุณสามารถดู:

คุณยังสามารถใช้การบีบอัดและเข้ารหัสตัวอักษร DNA ของคุณแต่ละตัวเป็น 2 บิตก่อนที่จะเก็บลงในอาร์เรย์เรียงลำดับ


บิล: คุณสามารถอธิบายได้ว่าข้อดีของqsortฟังก์ชั่นนี้มีมากกว่าstd::sortฟังก์ชั่นที่จัดทำโดย C ++? โดยเฉพาะอย่างยิ่งหลังใช้การรวบรวมข้อมูลที่มีความซับซ้อนสูงในห้องสมุดที่ทันสมัยและอินไลน์การดำเนินการเปรียบเทียบ ฉันไม่ได้ซื้อการอ้างสิทธิ์ที่ดำเนินการใน O (n) สำหรับกรณีส่วนใหญ่เนื่องจากจะต้องมีระดับวิปัสสนาที่ไม่มีในกรณีทั่วไป (อย่างน้อยก็ไม่มีค่าใช้จ่ายมาก )
Konrad Rudolph

ฉันไม่ได้ใช้ c ++ แต่ในการทดสอบของฉันอินไลน์ QSORT สามารถเร็วกว่า qsort ใน stdlib 3 เท่า ti7qsort เป็นการเรียงลำดับที่เร็วที่สุดสำหรับจำนวนเต็ม (เร็วกว่าแบบอินไลน์ QSORT) คุณยังสามารถใช้เพื่อจัดเรียงข้อมูลขนาดคงที่ขนาดเล็กได้ คุณต้องทำการทดสอบกับข้อมูลของคุณ
เรียกเก็บเงิน

1

การจัดเรียงของ MSB radix ของ dsimcha ดูดี แต่นิลส์เข้าใกล้หัวใจของปัญหาด้วยการสังเกตว่าท้องถิ่นแคชคือสิ่งที่ฆ่าคุณด้วยปัญหาที่มีขนาดใหญ่

ฉันขอแนะนำวิธีการง่าย ๆ :

  1. ประเมินขนาดที่ใหญ่ที่สุดmซึ่งการเรียงแบบฐานมีประสิทธิภาพ
  2. อ่านบล็อคของmองค์ประกอบในแต่ละครั้ง radix จัดเรียงและเขียนออก (ไปยังบัฟเฟอร์หน่วยความจำหากคุณมีหน่วยความจำเพียงพอ แต่อย่างอื่นไปยังไฟล์) จนกว่าคุณจะหมดการป้อนข้อมูลของคุณ
  3. ผสานบล็อกที่เรียงลำดับผลลัพธ์

การรวมเป็นอัลกอริทึมการเรียงลำดับที่ง่ายต่อการแคชมากที่สุดที่ฉันทราบ: "อ่านรายการถัดไปจากอาร์เรย์ A หรือ B จากนั้นเขียนรายการไปยังบัฟเฟอร์ขาออก" มันทำงานได้อย่างมีประสิทธิภาพในเทปไดรฟ์ มันต้องใช้2nพื้นที่ในการจัดเรียงnรายการ แต่การเดิมพันของฉันคือว่าพื้นที่แคชที่ดีขึ้นมากที่คุณเห็นจะทำให้ไม่สำคัญ - และถ้าคุณใช้การเรียงลำดับแบบไม่ใช้แทนคุณต้องการพื้นที่เพิ่มเติมนั้น

โปรดทราบว่าในที่สุดการผสานสามารถดำเนินการได้โดยไม่ต้องเรียกซ้ำและในความเป็นจริงการทำเช่นนี้จะทำให้ชัดเจนถึงรูปแบบการเข้าถึงหน่วยความจำเชิงเส้นที่แท้จริง


1

ดูเหมือนว่าคุณจะแก้ไขปัญหาได้แล้ว แต่สำหรับเร็กคอร์ดนั้นดูเหมือนว่าการเรียงลำดับแบบ Radix แบบแทนที่ได้ในรุ่นเดียวคือ "American Flag Sort" มีคำอธิบายที่นี่: วิศวกรรม Radix เรียง แนวคิดทั่วไปคือการส่งผ่าน 2 ตัวอักษรในแต่ละตัวอักษร - ก่อนอื่นให้นับจำนวนตัวอักษรแต่ละตัวที่คุณมีเพื่อให้คุณสามารถแบ่งอาร์เรย์อินพุตให้เป็นถังขยะได้ จากนั้นไปอีกครั้งสลับองค์ประกอบแต่ละอย่างลงในถังขยะที่ถูกต้อง ตอนนี้เรียงลำดับถังขยะซ้ำในตำแหน่งตัวละครต่อไป


ที่จริงแล้วโซลูชันที่ฉันใช้นั้นเกี่ยวข้องกับอัลกอริทึมการจัดเรียงธงมาก ฉันไม่รู้ว่ามีความแตกต่างที่เกี่ยวข้องหรือไม่
Konrad Rudolph

2
ไม่เคยได้ยินเกี่ยวกับ American Flag Sort แต่เห็นได้ชัดว่านั่นคือสิ่งที่ฉันเขียน: coliru.stacked-crooked.com/a/94eb75fbecc39066 ขณะนี้มันมีประสิทธิภาพสูงกว่าstd::sortและฉันแน่ใจว่า digitizer หลายตัวสามารถทำงานได้เร็วขึ้น แต่ชุดทดสอบของฉันมีหน่วยความจำ ปัญหา (ไม่ใช่อัลกอริทึม, ชุดทดสอบตัวเอง)
Mooing Duck

@ KonradRudolph: ความแตกต่างที่ยิ่งใหญ่ระหว่างการเรียงธงและการเรียงลำดับเลขฐานอื่น ๆ คือการนับผ่าน คุณพูดถูกว่าทุกประเภทของ radix มีความสัมพันธ์กันมาก แต่ฉันจะไม่ถือว่าคุณเป็นคนจัดเรียงธง
Mooing Duck

@MooingDuck: เพียงแค่รับแรงบันดาลใจจากตัวอย่างของคุณที่นั่น - ฉันติดอยู่ในการใช้งานที่เป็นอิสระของฉันและคุณช่วยให้ฉันกลับมาติดตาม ขอบคุณ! การปรับให้เหมาะสมที่เป็นไปได้อย่างหนึ่ง - ฉันยังไม่ได้ไปไกลพอที่นี่เพื่อดูว่ามันคุ้มค่าหรือไม่: หากองค์ประกอบในตำแหน่งที่คุณสลับไปเกิดขึ้นแล้วว่าเป็นที่ที่คุณต้องการคุณอาจต้องข้ามสิ่งนั้น ไม่ใช่ การตรวจจับสิ่งนี้จะต้องใช้ตรรกะพิเศษแน่นอนและพื้นที่เก็บข้อมูลเพิ่มเติมที่เป็นไปได้ด้วยเช่นกัน แต่เนื่องจากการแลกเปลี่ยนมีราคาแพงเมื่อเปรียบเทียบกับการเปรียบเทียบจึงอาจคุ้มค่าที่จะทำ
500 - ข้อผิดพลาดเซิร์ฟเวอร์ภายใน

1

ก่อนอื่นให้คิดถึงการเข้ารหัสปัญหาของคุณ กำจัดสตริงแทนที่ด้วยการเป็นตัวแทนไบนารี ใช้ไบต์แรกเพื่อระบุความยาว + การเข้ารหัส หรือใช้การแสดงความยาวคงที่ที่ขอบเขตสี่ไบต์ จากนั้นการเรียงลำดับของ Radix จะง่ายขึ้นมาก สำหรับการเรียงตัวของ Radix สิ่งที่สำคัญที่สุดคือไม่มีข้อยกเว้นในการเคลื่อนย้าย ณ จุดร้อนของวงใน

ตกลงฉันคิดอีกเล็กน้อยเกี่ยวกับปัญหา 4 นารี คุณต้องการวิธีแก้ปัญหาเช่นต้นไม้จูดี้สำหรับเรื่องนี้ วิธีแก้ไขปัญหาถัดไปสามารถจัดการกับสตริงความยาวผันแปรได้ สำหรับความยาวคงที่เพียงแค่เอาบิตความยาวซึ่งทำให้ง่ายขึ้น

จัดสรรบล็อกของ 16 พอยน์เตอร์ บิตที่มีนัยสำคัญน้อยที่สุดสามารถนำกลับมาใช้ใหม่ได้เนื่องจากบล็อกของคุณจะถูกจัดตำแหน่งเสมอ คุณอาจต้องการตัวจัดสรรพื้นที่เก็บข้อมูลพิเศษสำหรับมัน (แบ่งที่เก็บข้อมูลขนาดใหญ่เป็นบล็อกขนาดเล็ก) บล็อกมีหลายประเภท:

  • การเข้ารหัสด้วยสตริงความยาวผันแปรได้ 7 บิต เมื่อพวกเขาเติมเต็มคุณจะแทนที่ด้วย:
  • ตำแหน่งเข้ารหัสอักขระสองตัวถัดไปคุณมีตัวชี้ 16 ตัวต่อบล็อกถัดไปซึ่งลงท้ายด้วย:
  • การเข้ารหัสบิตแมปของอักขระสามตัวสุดท้ายของสตริง

สำหรับบล็อกแต่ละประเภทคุณต้องเก็บข้อมูลต่าง ๆ ไว้ใน LSB เนื่องจากคุณมีสตริงที่มีความยาวผันแปรคุณต้องจัดเก็บ end-of-string ด้วยและบล็อกประเภทสุดท้ายสามารถใช้กับสตริงที่ยาวที่สุดเท่านั้น ควรเปลี่ยนบิตความยาว 7 บิตให้น้อยลงเมื่อคุณเจาะลึกลงไปในโครงสร้าง

สิ่งนี้จะช่วยให้คุณมีการจัดเก็บสตริงที่เรียงลำดับอย่างรวดเร็วและมีประสิทธิภาพ มันจะทำงานคล้ายTrie ในการทำให้การทำงานนี้ต้องสร้างหน่วยทดสอบให้เพียงพอ คุณต้องการความครอบคลุมของการเปลี่ยนบล็อกทั้งหมด คุณต้องการเริ่มต้นด้วยบล็อกประเภทที่สองเท่านั้น

เพื่อประสิทธิภาพที่เพิ่มขึ้นคุณอาจต้องการเพิ่มประเภทบล็อกที่แตกต่างกันและบล็อกที่มีขนาดใหญ่ขึ้น หากบล็อกมีขนาดเท่ากันและใหญ่พอคุณสามารถใช้บิตน้อยกว่าสำหรับพอยน์เตอร์ได้ ด้วยขนาดบล็อก 16 พอยน์เตอร์คุณมีไบต์ว่างในพื้นที่ที่อยู่แบบ 32 บิตแล้ว ดูเอกสารต้นไม้ของ Judy สำหรับประเภทบล็อกที่น่าสนใจ โดยทั่วไปคุณเพิ่มรหัสและเวลาวิศวกรรมสำหรับการแลกเปลี่ยนพื้นที่ (และรันไทม์)

คุณอาจต้องการเริ่มต้นด้วย radix กว้าง 256 โดยตรงสำหรับอักขระสี่ตัวแรก นั่นให้พื้นที่ / เวลาที่เหมาะสม ในการใช้งานนี้คุณจะได้รับค่าใช้จ่ายหน่วยความจำน้อยกว่ามากด้วย trie ง่าย ๆ ; มันเล็กกว่าประมาณสามเท่า (ฉันไม่ได้วัด) O (n) ไม่มีปัญหาหากค่าคงที่ต่ำพอดังที่คุณสังเกตเห็นเมื่อเปรียบเทียบกับ quicksort O (n log n)

คุณสนใจที่จะจัดการคู่ผสมหรือไม่? ด้วยลำดับสั้น ๆ จะมี การปรับบล็อกเพื่อจัดการกับการนับนั้นเป็นเรื่องยุ่งยาก แต่ก็สามารถประหยัดพื้นที่ได้มาก


ฉันไม่เห็นว่าการเรียงลำดับของ Radix จะง่ายขึ้นในกรณีของฉันได้อย่างไรหากฉันใช้การแสดงผลแบบบิต อย่างไรก็ตามกรอบงานที่ฉันใช้นั้นให้ความเป็นไปได้ในการใช้การเป็นตัวแทนบิตอัดแน่น แต่นี่เป็นสิ่งที่โปร่งใสสำหรับฉันในฐานะผู้ใช้ส่วนต่อประสาน
Konrad Rudolph

ไม่เมื่อคุณมองไปที่นาฬิกาจับเวลาของคุณ :)
สเตฟาน Eggermont

ฉันจะได้เห็นต้นจูดีอย่างแน่นอน วานิลลาพยายามที่จะไม่นำมาที่โต๊ะมากนักเพราะมันทำตัวคล้ายกับรังสี MSD ธรรมดาที่ผ่านองค์ประกอบน้อยลง แต่ต้องการพื้นที่จัดเก็บเพิ่มเติม
Konrad Rudolph
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.