Сортування за місцем Radix


200

Це довгий текст. Будь ласка, нехай зі мною. Зникла питання: чи є дієвий алгоритм сортування радіоактивних місць ?


Попередній

У мене величезна кількість невеликих рядків фіксованої довжини, які використовують лише літери "A", "C", "G" і "T" (так, ви вже здогадалися: ДНК ), які я хочу сортувати.

На даний момент я використовую, std::sortякий використовує інтросорт у всіх поширених реалізаціях STL . Це працює досить добре. Однак я переконаний, що сортування radix ідеально відповідає моїй проблемі, яка постає перед нами і повинна працювати набагато краще на практиці.

Деталі

Я перевірив це припущення з дуже наївною реалізацією, і для відносно невеликих вкладів (порядку 10 000) це було правдою (ну, принаймні, удвічі швидше). Однак час виконання погіршується безсильно, коли розмір проблеми збільшується ( N > 5 000 000).

Причина очевидна: radix сортування вимагає копіювання всіх даних (фактично не один раз в моїй наївній реалізації). Це означає, що я помістив ~ 4 Гб в основну пам'ять, яка, очевидно, вбиває продуктивність. Навіть якщо цього не сталося, я не можу дозволити собі використовувати стільки пам’яті, оскільки розміри проблем фактично стають ще більшими.

Використовуйте випадки

В ідеалі цей алгоритм повинен працювати з будь-якою довжиною рядка від 2 до 100, як для ДНК, так і для ДНК5 (що дозволяє отримати додатковий символ символу “N”) або навіть ДНК з кодами неоднозначності IUPAC (в результаті чого 16 різних значень). Однак я розумію, що всі ці випадки не можуть бути охоплені, тому я задоволений будь-яким покращенням швидкості, яку я отримую. Код може динамічно визначати, до якого алгоритму відправити.

Дослідження

На жаль, стаття у Вікіпедії про сортування radix марна. Розділ про місцевий варіант - це повний сміття. Розділ NIST-DADS на сортировці radix знаходиться поруч із неіснуючим. Існує багатообіцяючий документ під назвою « Ефективна адаптивна сортировка на місці», який описує алгоритм «MSL». На жаль, цей документ теж розчаровує.

Зокрема, є такі речі.

По-перше, алгоритм містить кілька помилок і залишає багато незрозумілих. Зокрема, він не деталізує виклик рекурсії (я просто припускаю, що він збільшує або зменшує деякий покажчик для обчислення значень поточного зсуву та маски). Крім того, він використовує функції dest_groupі dest_addressне даючи визначень. Я не бачу, як ефективно їх реалізувати (тобто в O (1); принаймні, dest_addressце не тривіально).

І останнє, але не менш важливе, алгоритм досягає місця на місцях шляхом заміни індексів масиву елементами всередині вхідного масиву. Це, очевидно, працює лише на числових масивах. Мені потрібно використовувати його на струнах. Звичайно, я міг би просто накрутити сильний набір тексту та йти вперед, припускаючи, що пам'ять буде терпіти моє зберігання індексу там, де йому не належить. Але це працює лише до тих пір, поки я можу втиснути свої рядки в 32 біти пам'яті (при 32-бітових цілих числах). Це всього 16 символів (давайте ігноруємо на той момент, що 16> log (5 000 000)).

Інший документ одного з авторів взагалі не дає точного опису, але він дає час виконання MSL як сублінійне, що виходить неправильним.

Для резюме : Чи є надія знайти реалізацію робочої довідки або хоча б хороший псевдокод / ​​опис робочого сорту radix на місці, який працює на ДНК-рядках?


65
Це одне чудово написане запитання.
JustinT

1
наскільки маленькі струни фіксованої довжини?
EvilTeach

1
@EvilTeach: Я додав випадки використання.
Конрад Рудольф

2
@Stephan: це все добре і добре. Але у випадку пропуску копіювання / кешу я просто затримуюсь. Що стосується пам’яті, я потрапив у філософію. Це просто не підлягає обговоренню. Усі ці фантазійні прийоми зберігання частин даних на диску, безумовно, повільніше, ніж поточне рішення швидкості.
Конрад Рудольф

2
(продовження) рішення dimimcha, з іншого боку, безумовно, швидше, ніж швидкий вибір у деяких вхідних даних. Кількість рухів може бути високою, а місце кешування невеликим, але в реальному світі це все-таки добре. Я також трохи змінив рішення, щоб зменшити кількість замінів, які мені потрібно виконати.
Конрад Рудольф

Відповіді:


61

Що ж, ось проста реалізація радіосигналу MSD для ДНК. Це написано на 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);
   }
}

Очевидно, що це щось специфічне для ДНК, на відміну від загального, але воно повинно бути швидким.

Редагувати:

Мені стало цікаво, чи працює цей код насправді, тому я перевірив / налагодив його, очікуючи запуску власного коду біоінформатики. Наведена вище версія фактично перевірена і працює. Для 10 мільйонів послідовностей по 5 основ кожна приблизно в 3 рази швидша, ніж оптимізований введення.


9
Якщо ви можете жити з підходом 2х пропусків, це поширюється на радікс-N: пропуск 1 = просто пройдіть і порахуйте, скільки їх є у кожної з N цифр. Тоді, якщо ви розділяєте масив, це вказує, з чого починається кожна цифра. Pass 2 робить свопи до відповідного положення в масиві.
Jason S

(наприклад, для N = 4, якщо є 90000 A, 80000 G, 100 C, 100000 T, тоді зробіть масив, ініційований на сукупні суми = [0, 90000, 170000, 170100], який використовується замість ваших APos, CPos і т. Д. Як курсор, на який слід замінити наступний елемент для кожної цифри.)
Jason S

Я не впевнений, яке співвідношення між бінарним представленням і цим рядковим поданням буде, крім використання принаймні в 4 рази більше пам'яті, ніж потрібно
Стефан Еггермонт

Яка швидкість з довшими послідовностями? Вам не вистачає різних, довжиною яких є 5
Стефан Еггермон

4
Цей радіаційний сорт виглядає як особливий випадок сорту американського прапора - добре відомий на місці варіант сортування радіаційного типу.
Едвард КМЕТТ

21

Я ніколи не бачив місцевого типу radix, і від природи сортування radix я сумніваюся, що це набагато швидше, ніж поза місцем до тих пір, поки тимчасовий масив вписується в пам'ять.

Причина:

Сортування виконує лінійне зчитування на вхідному масиві, але всі записи будуть майже випадковими. Від певного N вгору це зводиться до пропуску кешу на запис. Ця помилка кешу - це те, що уповільнює ваш алгоритм. Якщо він стоїть на місці чи ні, цей ефект не зміниться.

Я знаю, що це не відповість на ваше запитання безпосередньо, але якщо сортування є вузьким місцем, ви, можливо, захочете переглянути алгоритми сортування як крок попередньої обробки (вікі-сторінка на soft-heap може почати роботу).

Це може дати дуже приємний приріст місцевості кешу. Позаштатний радіологічний сорт з підручника тоді буде краще. Записи все ще будуть майже випадковими, але, принаймні, вони будуть згуртовуватися навколо тих самих фрагментів пам'яті і, таким чином, збільшують коефіцієнт хітів кешу.

Я навіть не маю уявлення, чи все виходить на практиці.

Btw: Якщо ви маєте справу лише з рядками ДНК: Ви можете стиснути табличку на два біти і запакувати свої дані досить багато. Це дозволить зменшити потребу в пам'яті на чотири фактора над наївним поданням. Адресація стає складнішою, але ALU вашого процесора має багато часу витратити протягом усіх кеш-пропусків у будь-якому випадку.


2
Два хороших моменти; поблизу сортування - це нова концепція для мене, мені доведеться прочитати про це. Кеш пропускає - ще одна думка, яка переслідує мої мрії. ;-) Мені доведеться про це подивитися.
Конрад Рудольф

Це для мене теж нове (пару місяців), але як тільки ви отримаєте концепцію, ви починаєте бачити можливості підвищення продуктивності.
Нільс Піпенбрінк

Записи далеко не майже випадкові, якщо ваш радіус не дуже великий. Наприклад, якщо припустити, що ви впорядковуєте один символ (сортування радикс-4), усі записи будуть входити в одне з чотирьох відросткових ліній. Це і кеш, і попереднє вилучення. Звичайно, ви можете скористатися більшим радіусом, і за деяким вказівником ви отримаєте компроміс між кешем та попередньою вибіркою та розміром радикса. Ви можете підштовхнути точку беззбитковості до більших радіусів, використовуючи попереднє завантаження програмного забезпечення або область подряпин для відра з періодичним переливанням у «справжні» відра.
BeeOnRope

8

Ви, звичайно, можете скинути вимоги до пам'яті, кодуючи послідовність у бітах. Ви переглядаєте перестановки так, для довжини 2 "ACGT" - це 16 станів, або 4 біти. Для довжини 3 це 64 стану, які можна закодувати в 6 біт. Таким чином, це виглядає як 2 біти на кожну букву в послідовності, або приблизно 32 біти на 16 символів, як ви сказали.

Якщо є спосіб зменшити кількість дійсних 'слів', можливе подальше стиснення.

Отже, для послідовностей довжиною 3 можна створити 64 відра, можливо розміром uint32 або uint64. Ініціалізуйте їх до нуля. Повторіть свій дуже великий список із 3-х послідовностей символів та кодуйте їх, як зазначено вище. Використовуйте це як індекс та збільшення цього відра.
Повторіть це, поки всі ваші послідовності не будуть оброблені.

Далі відновіть свій список.

Ітерація через 64 ​​відра для того, щоб підрахунок, знайдений у цьому відрі, генерував багато примірників послідовності, представленої цим відром.
коли всі відра були повторені, ви маєте відсортований масив.

Послідовність 4, додає 2 біта, так що було б 256 відра. Послідовність 5, додає 2 біти, тож було б 1024 відра.

У якийсь момент кількість відра наблизиться до ваших меж. Якщо ви читаєте послідовності з файлу, замість того, щоб зберігати їх у пам'яті, для відра буде доступно більше пам’яті.

Я думаю, що це було б швидше, ніж робити сортування in situ, оскільки відра, ймовірно, помістяться у вашому робочому наборі.

Ось хак, який показує техніку

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

Навіщо порівнювати, коли можна хеш?
wowo

1
Чорт прямо. Продуктивність, як правило, є проблемою будь-якої обробки ДНК.
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 загальних відра), таким чином ви зробите менше гілок буфера на основі диска. Це може або не покращить продуктивність, тому експериментуйте з цим.


Для деяких додатків ми використовуємо файли, зіставлені з пам'яттю. Однак в цілому ми працюємо при припущенні, що машина забезпечує лише ледве об'єм оперативної пам'яті, щоб не вимагати явного резервного копіювання диска (звичайно, обмін все ще відбувається). Але ми вже розробляємо механізм автоматичних масивів, захищених від диска
Конрад Рудольф

6

Я збираюся вийти на кінцівку і запропоную вам перейти на реалізацію купи / кучки . Ця пропозиція має деякі припущення:

  1. Ви контролюєте зчитування даних
  2. Ви можете зробити щось змістовне з відсортованими даними, як тільки ви «почнете» їх сортувати.

Краса купи / нагромадження полягає в тому, що ви можете створювати купу під час читання даних, і ви можете почати отримувати результати в той момент, коли ви створили купу.

Давайте відступимо. Якщо вам так пощастило, що ви можете прочитати дані асинхронно (тобто ви можете опублікувати якийсь запит на читання і отримати повідомлення, коли деякі дані будуть готові), а потім ви зможете побудувати шматок купи, поки ви чекаєте на наступний фрагмент даних, що надходять - навіть з диска. Часто такий підхід може поховати більшу частину вартості половини вашого сортування за час, витрачений на отримання даних.

Після того, як ви прочитаєте дані, перший елемент уже доступний. Залежно від того, куди ви надсилаєте дані, це може бути чудово. Якщо ви надсилаєте його на інший асинхронний зчитувач, чи якусь паралельну модель "події", або інтерфейс користувача, ви можете надсилати шматки та шматки під час проходження.

Це говорило - якщо ви не маєте контролю над тим, як дані читаються, і вони читаються синхронно, і ви не маєте користі для відсортованих даних, поки цілком не виписано - ігноруйте все це. :(

Дивіться статті у Вікіпедії:


1
Гарна пропозиція. Однак я вже спробував це, і в моєму конкретному випадку витрата на підтримку купи більший, ніж просто накопичення даних у векторі та сортування, коли всі дані надійдуть.
Конрад Рудольф


4

Для продуктивності ви можете переглянути більш загальні алгоритми сортування рядків.

В даний час ви закінчуєте торкатися кожного елемента кожної струни, але ви можете зробити краще!

Зокрема, вибуховий сорт дуже добре підходить для цієї справи. Оскільки бонус, оскільки burstsort заснований на спробах, він працює смішно добре для малих розмірів алфавіту, які використовуються в ДНК / РНК, оскільки вам не потрібно вбудовувати будь-яку схему стиснення потрійного пошуку, хеш чи іншу схему стиснення вузла трійки в реалізація трійки. Спроби можуть бути корисними і для вашої кінцевої мети, подібної до суфікса.

Достойна реалізація burstsort загального призначення доступна на ковані джерела на веб-сайті http://sourceforge.net/projects/burstsort/ - але це не на місці.

Для порівняння, реалізація C-burstsort розглянута на веб-сторінці http://www.cs.mu.oz.au/~rsinha/papers/SinhaRingZobel-2006.pdf порівняльних показників на 4-5 разів швидше, ніж сорти швидкості та радіації для деяких типових навантажень.


Мені обов'язково доведеться подивитися на вибухові сорти - хоча на даний момент я не бачу, як трійка могла б бути побудована на місці. Загалом, суфіксні масиви мають у біоінформатиці всі, крім замінених дерев суфіксів (і, таким чином, спроби), завдяки чудовим характеристикам продуктивності у практичних програмах.
Конрад Рудольф

4

Ви захочете ознайомитись з широкомасштабною обробкою послідовності геномів від доктора. Касахара та Морішита.

Струни, що складаються з чотирьох нуклеотидних букв A, C, G і T, можуть бути спеціально закодовані в цілі числа для набагато швидшої обробки. Сорт Radix серед багатьох алгоритмів, про які йдеться в книзі; ви повинні мати можливість адаптувати прийняту відповідь на це запитання та побачити велике покращення продуктивності.


Сорт радікса, представлений у цій книзі, не є на місці, тому він не може бути використаний для цієї мети. Що стосується ущільнення рядків, то я, звичайно, це вже роблю. Моє (більш-менш) остаточне рішення (розміщено нижче) не показує цього, оскільки бібліотека дозволяє мені трактувати їх як звичайні рядки - але використовуване RADIXзначення, звичайно, може бути адаптоване до більших значень.
Конрад Рудольф

3

Ви можете спробувати використати трійку . Сортування даних - це просто ітерація набору даних та їх вставлення; структура природно сортована, і ви можете вважати її подібною до B-Дерева (крім того, замість того, щоб робити порівняння, ви завжди використовуєте непрямі напрямки).

Поведінка кешування буде корисною для всіх внутрішніх вузлів, тому ви, ймовірно, не покращуєте це; але ви можете зіткнутися і з коефіцієнтом розгалуження вашого трійця (переконайтесь, що кожен вузол вписується в одну лінію кешу, виділіть вузли трійки, подібні до купи, як суміжний масив, який представляє обхід рівня рівня). Оскільки спроби є також цифровими структурами (O (k) вставляти / знаходити / видаляти для елементів довжини k), ви повинні мати конкурентоспроможні характеристики для радіальної сортування.


У трійці є та сама проблема, що і в моїй наївної реалізації: для неї потрібна додаткова пам'ять O (n), якої просто занадто багато.
Конрад Рудольф

3

Я б розпочав розбір упакованого зображення рядків. Як стверджується, Burstsort має набагато кращу локальність, ніж радіаційні сорти, зменшуючи додаткове використання місця при спробах вибуху замість класичних спроб. Оригінальний папір має виміри.


2

Radix-Sort не є кешованим і не є найшвидшим алгоритмом сортування для великих наборів. Ви можете подивитися:

Ви також можете використовувати стиснення та кодувати кожну букву вашої ДНК у 2 біти, перш ніж зберігати в масив сортування.


законопроект: Ви могли б пояснити, які переваги qsortмає ця функція перед std::sortфункцією, що надається C ++? Зокрема, остання впроваджує в сучасних бібліотеках дуже складний інтросоркт та окреслює операцію порівняння. Я не купую стверджують , що він виконує в O (N) для більшості випадків, так як це вимагало б ступінь самоаналізу не доступні в загальному випадку (принаймні , НЕ багато накладних витрат).
Конрад Рудольф

Я не використовую c ++, але в моїх тестах вбудований QSORT може бути в 3 рази швидшим, ніж qsort у stdlib. Ti7qsort - це найшвидший сорт для цілих чисел (швидше, ніж вбудований QSORT). Ви також можете використовувати їх для сортування невеликих даних фіксованого розміру. Ви повинні зробити тести зі своїми даними.
законопроект

1

Сорт радіації MSB dsimcha виглядає приємно, але Нілс наближається до суті проблеми, спостерігаючи, що локалізація кешу - це те, що вбиває вас при великих розмірах проблеми.

Я пропоную дуже простий підхід:

  1. Емпірично оцініть найбільший розмір, mдля якого ефективний сорт радіації.
  2. Одночасно читайте блоки mелементів, радіосортуйте їх і записуйте їх (у буфер пам’яті, якщо у вас є достатня кількість пам’яті, але інакше для файлу), поки не вичерпаєте свої дані.
  3. Об'єднайте отримані сортовані блоки.

Mergesort - це найбільш зручний кеш-алгоритм сортування, про який я знаю: "Прочитайте наступний елемент з будь-якого масиву A або B, а потім запишіть елемент у вихідний буфер." Він працює ефективно на стрічкових накопичувачах . 2nДля сортування nелементів потрібен простір , але я думаю, що значно покращене місце кешу, яке ви побачите, зробить це неважливим - і якщо ви використовували не місце на місце radix, вам потрібен був додатковий простір у будь-якому випадку.

Зверніть увагу, нарешті, що об'єднання об'єднань може бути реалізовано без рекурсії, і фактично це робить чіткою справжню лінійну схему доступу до пам'яті.


1

Схоже, ви вирішили проблему, але для запису виявляється, що однією з версій працездатного in-place radix сортування є "Американський сорт прапора". Тут описано: Engineering Radix Sort . Загальна ідея полягає в тому, щоб зробити два пропуски на кожен символ - спочатку порахуйте, скільки у вас є, щоб ви могли поділити вхідний масив на біни. Потім знову пройдіться, поміняючи кожен елемент у правильний контейнер. Тепер рекурентно сортуйте кожен контейнер за наступною позицією символів.


Власне, рішення, яке я використовую, дуже тісно пов'язане з алгоритмом сортування прапорців. Я не знаю, чи є якесь відповідне розрізнення.
Конрад Рудольф

2
Ніколи не чув про сортування американського прапора, але, мабуть, саме це я зашифрував : coliru.stacked-crooked.com/a/94eb75fbecc39066 Наразі це перевершує результативність std::sort, і я впевнений, що багатоцифровий цифровізатор може працювати швидше, але мій тестовий набір має пам'ять проблеми (не алгоритм, сам тестовий набір)
Mooing Duck

@KonradRudolph: Велика відмінність між сортом прапор та іншими видами радіації - це лічильний прохід. Ви маєте рацію, що всі види радіації дуже тісно пов'язані, але я б не вважав твій сорт прапором.
Mooing Duck

@MooingDuck: Я просто взяв натхнення з вашого зразка там - я застряг у власній незалежній реалізації, і твоя допомогла мені повернутися назад. Дякую! Одна можлива оптимізація - я ще не зайшов досить далеко, щоб побачити, чи варто ще цього: Якщо елемент у позиції, на яку ви поміняєте TO, трапиться вже там, де це має бути, ви можете пропустити це та перейти до того, що ні. Виявлення цього вимагає, звичайно, додаткової логіки та можливого додаткового зберігання, але оскільки заміни є дорогими порівняно з порівняннями, можливо, це варто зробити.
500 - Внутрішня помилка сервера

1

Спочатку подумайте про кодування вашої проблеми. Позбавтеся від рядків, замініть їх двійковим поданням. Використовуйте перший байт, щоб вказати довжину + кодування. Як варіант, використовувати подання з фіксованою довжиною на чотирьохбайтовій межі. Тоді сортування радіусу стає набагато простішим. Для радіаційного сорту найважливіше - не мати винятку обробки в гарячому місці внутрішньої петлі.

Добре, я трохи більше подумав про проблему 4-х нарій. Ви хочете для цього рішення, як Джуді . Наступне рішення може обробляти рядки змінної довжини; для фіксованої довжини просто видаліть біти довжини, що насправді полегшує.

Виділяють блоки з 16 покажчиків. Найменш значущий біт покажчиків можна повторно використовувати, оскільки ваші блоки завжди будуть вирівнюватися. Вам може знадобитися спеціальний розподільник пам’яті для нього (розбиваючи великі сховища на більш дрібні блоки). Існує ряд різних типів блоків:

  • Кодування 7 бітами довжини рядків змінної довжини. Коли вони заповнюються, ви замінюєте їх на:
  • Позиція кодує наступні два символи, у вас є 16 покажчиків на наступні блоки, що закінчується на:
  • Растрове кодування останніх трьох символів рядка.

Для кожного виду блоків потрібно зберігати різну інформацію в LSB. Оскільки у вас є рядки змінної довжини, вам також потрібно зберігати кінець рядка, і останній тип блоку можна використовувати лише для найдовших рядків. 7 біт довжини слід замінити на менші, як ви заглиблюєтеся в структуру.

Це забезпечує вам досить швидке і дуже ефективне зберігання впорядкованих рядків. Це буде поводитись десь як трійка . Щоб це працювало, переконайтеся, що склали достатню кількість тестів. Ви хочете охопити всі переходи блоків. Ви хочете почати лише з другого типу блоку.

Для більшої продуктивності ви можете додати різні типи блоків та більший розмір блоку. Якщо блоки завжди однакового розміру і досить великі, ви можете використовувати ще менше біт для покажчиків. Маючи розмір блоку в 16 покажчиків, у вас вже є байт вільний у 32-бітному адресному просторі. Ознайомтеся з документацією дерева дерева Джуді щодо цікавих типів блоків. В основному, ви додаєте код та інженерний час для простору (та часу виконання)

Напевно, ви хочете почати з 256 широких прямих радіацій для перших чотирьох символів. Це забезпечує гідний проміжок часу та часу. У цій реалізації ви отримуєте набагато менше накладних витрат на пам'ять, ніж у простого трійника; це приблизно втричі менше (я не вимірював). O (n) не є проблемою, якщо константа є досить низькою, як ви помітили, порівнюючи з O (n log n) швидким косом.

Вас цікавить поводження з парними? З короткими послідовностями, це буде. Пристосування блоків для обробки підрахунків є складним, але це може бути дуже простором.


Я не бачу, як радіо сортування стає простішим у моєму випадку, якщо я використовую розрядне подання. До речі, фреймворк, який я використовую, фактично надає можливість використання розфасованого представлення, але це для мене як користувача інтерфейсу абсолютно прозоро.
Конрад Рудольф

Не тоді, коли дивишся на свою секундомір :)
Стефан Еггермонт

Я обов'язково погляну на дерева Джуді. Спроби ванілі насправді не приносять багато на стіл, хоча вони поводяться в основному як звичайний MSD-радикальний сорт з меншими пропусками по елементах, але потребують додаткового зберігання.
Конрад Рудольф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.