Программа 10.3. Трехпутевая поразрядная быстрая сортировка
Программный код поразрядной сортировки MSD фактически мало чем отличается от программного кода быстрой сортировки с трехпутевым разбиением (программа 9.5), отличия состоят в следующем: (/) ссылки на ключи становятся ссылками на конкретные байты ключей, (//) текущий байт добавляется как параметр к рекурсивной служебной программе и (///) рекурсивные вызовы для среднего подфайла перемещаются к следующему байту. Мы избегаем выполнять перемещения за пределы концов строк путем проверки, равно ли разделяющее значение 0, перед рекурсивными обращениями, которые обеспечивают переход к следующим байтам. Когда разделяющим значением является 0, левый подфайл пуст, средний подфайл соответствует ключам, которые, по определению программы, равны этому значению, а правый подфайл соответствует более длинным строкам, которые требуют дальнейшей обработки.
#define ch(A) digit(A, d)
template <class Item>
{
int i, j, k, p, q; int v;
if (r-1 <= M) { insertion (a, 1, r) ; return; }
v = ch(a[r]); i = 1-1; j = r; p = 1-1; q = r; while (i < j)
{
while (ch(a[++i]) < v) ;
while (v < ch(a[- - j])) if (j == 1) break;
if (i > j) break;
exch(a[i], a[j]);
if (ch(a[i])==v) { p++; exch(a[p], a[i]); }
if (v==ch(a[j])) { q--; exch(a[j], a[q]); }
}
if (p == q)
{ if (v != "\0") quicksortX(a, 1, r, d+1) ; return; }
if (ch(a[i]) < v) i++;
for (k = 1; k <= p; k++, j--) exch(a[k], a[j]);
for (k = r; k >= q; k--, i++) exch(a[k], a[i]);
quicksortX(a, 1, j, d) ;
if ((i == r) && (ch(a[i]) == v)) i++;
if (v != "\0") quicksortX(a, j+1, i-1, d+1) ; quicksortX(a, i, r, d) ;
}
РИСУНОК 10.12. РЕКУРСИВНАЯ СТРУКТУРА ТРЕХПУТЕВОЙ ПОРАЗРЯДНОЙ БЫСТРОЙ СОРТИРОВКИ
Представленная на диаграмме комбинация двоичного и троичного деревьев соответствует подстановке 26-путевых узлов в двоичное дерево, изображенное на рис. 10.10, троичных деревьев поиска, как показано на рис. 10.13. Любой путь от корня на нижний уровень дерева, который жанчивается средней связью, определяет ключ файла, задаваемый символами, охваченными средними связями на этом пути. В диаграмме, показанной на рис. 10.10, имеется 1035 пустых связей, которые не отображены на ней, что касается рассматриваемой диаграммы, то все 155 пустых связей, которыми обладает это дерево, на диаграмме показаны. Каждая пустая связь соответствует пустой корзине, так что это различие показывает, как трехпутевой разбиение может существенно сократить число пустых корзин, которые появляются во время поразрядной сортировки MSD.
Рассмотрим случай, когда ключи имеют большую длину (для простоты предположим, что длина ключей фиксирована) и в то же время по большей части старшие байты одинаковы. В подобного рода ситуациях время выполнения обычной быстрой сортировки пропорционально длине слова, помноженной на 27Vln7V, тогда как время выполнения ее поразрядной версии пропорционально N, умноженному на длину слова (чтобы обнаружить все равные между собой старшие байты) плюс 2N\r\N (затрачивается на сортировку более коротких ключей). Иначе говоря, этот метод работает в ln/V раз быстрее, чем обычная быстрая сортировка, если принимать во внимание только стоимость операций сравнения. Для ключей, используемых в сортировочных приложениях на практике, характеристики, подобные полученным в условиях рас-смотренного выше искусственного примера, не являются чем-то необычным (см. упражнение 10.25).
Другим интересным свойством трехпутевой поразрядной быстрой сортировки является то, что ее характеристики напрямую не зависят от значения основания системы счисления. Для других методов сортировки, использующих основание системы счисления, нужно заводить и поддерживать вспомогательный массив, индексированный по значению основания системы счисления. Также следует позаботиться о том, чтобы размер этого массива ненамного превосходил размер самого файла. Для этого метода нет такой таблицы. Если в качестве основания брать исключительно большое значение (больше длины слова), то рассматриваемый метод превращается в обычную быструю сортировку, а если в качестве основания взять число 2, то он вы-эождается в обычную двоичную быструю сортировку, и только промежуточные значения основания системы счисления позволяют эффективно преодолевать равные промежутки между фрагментами ключей.
Мы можем разработать гибридный метод, пригодный для многих практических приложений, обладающий превосходными характеристиками, применяя стандартную поразрядную сортировку MSD для упорядочения крупных файлов, чтобы воспользоваться преимуществами многопутевого разбиения, и трехпутевую поразрядную сортировку с небольшим значением основания системы счисления для небольших файлов, чтобы избежать отрицательных эффектов, обусловленных наличием большого числа пустых корзин.
Трехпутевая быстрая сортировка успешно применяется и в тех случаях, когда сортируемыми ключами являются векторы (как в математическом смысле, так и в смысле стандартной библиотеки шаблонов C++. Другими словами, если ключи составлены из независимых компонентов (каждый компонент сам по себе независимый ключ), мы имеем возможность переупорядочить записи таким образом, что они будут располагаться в порядке следования первых компонентов ключей и в порядке следования вторых компонентов ключей в тех случаях, когда равны их первые компоненты и т.д. Мы можем рассматривать сортировку векторов как обобщенный вид поразрядной сортировки, где R может быть произвольно большим. После выполнения настройки программы 10.3 на это приложение, мы будем называть ее многомерной быстрой сортировкой (multikey quicksort).
