Сортировка Хана
Сортировка Хана (Yijie Han) — сложный алгоритм сортировки целых чисел со сложностью , где — количество элементов для сортировки.
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.
Содержание
- 1 Алгоритм
- 2 Andersson's exponential search tree
- 3 Необходимая информация
- 4 Уменьшение числа бит в числах
- 5 Signature sorting
- 6 Сортировка на маленьких целых
- 7 Сортировка [math]n[/math] целых чисел в [math]\sqrt{n}[/math] наборов
- 8 Собственно сортировка с использованием [math]O(nloglogn) времени и памяти[/math]
Алгоритм
Алгоритм построен на основе экспоненциального поискового дерева (далее — Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.
Andersson's exponential search tree
Э.П.дерево с листьями состоит из корня и (0<<1) Э.П.поддеревьев, в каждом из которых листьев; каждое Э.П.поддерево является сыном корня . В этом дереве уровней. При нарушении баланса дерева, необходимо балансирование, которое требует времени при вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.
Необходимая информация
| Определение: |
| Контейнер — объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д. |
| Определение: |
| Алгоритм сортирующий целых чисел из множества называется консервативным, если длина контейнера (число бит в контейнере), является . Если длина больше, то алгоритм неконсервативный. |
| Определение: |
| Если мы сортируем целые числа из множества {0, 1, ..., - 1} с длиной контейнера с >= 1, тогда мы сортируем с неконсервативным преимуществом . |
| Определение: |
| Для множества определим
min() = min(: принадлежит ) max() = max(: принадлежит ) Набор < если max() <= min() |
Уменьшение числа бит в числах
Один из способов ускорить сортировку — уменьшить число бит в числе. Один из способов уменьшить число бит в числе — использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до . Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования чисел в таблицу размера за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.
Алгоритм: Пусть целое число и пусть . Класс хэш функций из в определен как и для всех из .
Данный алгоритм базируется на следующей лемме:
Номер один.
| Лемма: |
Даны целые числа >= >= 0 и является подмножеством {0, ..., - 1}, содержащим элементов, и >= С. Функция принадлежащая может быть выбрана за время так, что количество коллизий |
Взяв мы получим хэш функцию которая захэширует чисел из в таблицу размера без коллизий. Очевидно, что может быть посчитана для любого за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod ) и (div ).
Такая хэш функция может быть найдена за .
Следует отметить, что несмотря на размер таблицы , потребность в памяти не превышает потому, что хэширование используется только для уменьшения количества бит в числе.
Signature sorting
В данной сортировке используется следующий алгоритм:
Предположим, что чисел должны быть сортированы, и в каждом бит. Мы рассматриваем, что в каждом числе есть сегментов, в каждом из которых бит. Теперь мы применяем хэширование ко всем сегментам и получаем бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке чисел по бит в каждом стала задачей по сортировке чисел по бит в каждом.
Так же, рассмотрим проблему последующего разделения. Пусть , , ..., — чисел и — множество чисeл. Мы хотим разделить в наборов таких, что: < {} < < {} < ... < {} < . Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в на сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из и оставим только один в каждом числе. По существу для каждого мы возьмем все сегментов. Если соответствующие сегменты и совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в , — сегмент, который выделяется из . Таким образом мы преобразуем начальную задачу о разделении чисел в бит в несколько задач на разделение с числами в бит.
Пример:
= 3, = 5, = 7, = 10, S = {1, 4, 6, 8, 9, 13, 14}.
Мы разделим числа на 2 сегмента. Для получим верхний сегмент 0, нижний 3; верхний 1, нижний 1; верхний 1, нижний 3; верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента ; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.
Сортировка на маленьких целых
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм.
Номер два.
| Лемма: |
целых чисел можно отсортировать в наборов , , ..., таким образом, что в каждом наборе чисел и < при < , за время и место с не консервативным преимуществом |
| Доказательство: |
| Доказательство данной леммы будет приведено далее в тексте статьи. |
Номер три.
| Лемма: |
Выбор -ого наибольшего числа среди чисел упакованных в контейнеров может быть сделана за время и с использованием места. Конкретно медиана может быть так найдена. |
| Доказательство: |
| Так как мы можем делать попарное сравнение чисел в одном контейнере с числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., -ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор из медиан теперь содержится в контейнерах. Рекурсивно находим медиану в . Используя уберем хотя бы чисел среди . Затем упакуем оставшиеся из контейнеров в контейнеров и затем продолжим рекурсию. |
Номер четыре.
| Лемма: |
Если целых чисел, в сумме использующие бит, упакованы в один контейнер, тогда чисел в контейнерах могут быть отсортированы за время , с использованием места. |
| Доказательство: |
|
Так как используется только бит в каждом контейнере для хранения чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает времени и места. Потому, что мы используем бит на контейнер нам понадобится шаблонов для всех контейнеров. Затем поместим контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более контейнеров которые не смогут образовать группу. Поэтому не более контейнеров не смогут сформировать группу. Для каждой группы мы помещаем -е число во всех контейнерах в один. Таким образом мы берем -целых векторов и получаем -целых векторов где -ый вектор содержит -ое число из входящего вектора. Эта транспозиция может быть сделана за время , с использованием места. Для всех групп это занимает время , с использованием места. Для контейнеров вне групп (которых штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более места и времени. После всего этого мы используем bucket sorting вновь для сортировки контейнеров. таким образом мы отсортируем все числа. |
Заметим, что когда мы сортируем чисел в контейнеров за время , с использованием O(n/g) места. Выгода очевидна.
Лемма пять.
| Лемма: |
Если принять, что каждый контейнер содержит бит, и чисел, в каждом из которых бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий бит, и маркеров упакованы в один контейнер таким же образом, что и числа, тогда чисел в контейнерах могут быть отсортированы по их маркерам за время с использованием места.
(*): если число упаковано как -ое число в -ом контейнере для чисел, тогда маркер для упакован как -ый маркер в -ом контейнере для маркеров. |
| Доказательство: |
| Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел. |
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.
Лемма шесть.
| Лемма: |
предположим, что каждый контейнер содержит бит, что чисел, в каждом из которых бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий бит, и что маркеров упакованы в один контейнер тем же образом что и числа, тогда чисел в контейнерах могут быть отсортированы по своим маркерам за время , с использованием памяти. |
| Доказательство: |
| Заметим, что несмотря на то, что длина контейнера бит всего бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем вместо контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей контейнеров мы сначала упаковываем контейнеров в контейнеров упаковывая контейнеров в один. Далее мы делаем транспозицию над контейнерами. Таким образом перемещение занимает всего времени для каждой группы и времени для всех чисел. После завершения транспозиции, мы далее распаковываем контейнеров в контейнеров. |
Заметим, что если длина контейнера и только бит используется для упаковки чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место , потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время .
Сортировка целых чисел в наборов
Постановка задачи и решение некоторых проблем:
Рассмотрим проблему сортировки целых чисел из множества {0, 1, ..., - 1} в наборов как во второй лемме. Мы предполагаем, что в каждом контейнере бит и хранит число в бит. Поэтому неконсервативное преимущество . Мы так же предполагаем, что . Иначе мы можем использовать radix sort для сортировки за время и линейную память. Мы делим бит, используемых для представления каждого числа, в блоков. Таким образом каждый блок содержит как минимум бит. -ый блок содержит с -ого по -ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется -уровневый алгоритм, который работает следующим образом:
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер ). Предполагаем, что биты этих м.ч. упакованы в контейнеров с м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих м.ч. за время и память . Пусть это найденная медиана. Тогда м.ч. могут быть разделены на не более чем три группы: , и . содержит м.ч. которые меньше , содержит м.ч. равные , содержит м.ч. большие . Так же мощность и <= . Мощность может быть любой. Пусть это набор чисел, у которых наибольший блок находится в . Тогда мы можем убрать убрать бит (наибольший блок) из каждого числа из из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только блоков, для каждого числа потребуется не более стадий чтобы поместить его в набор половинного размера. За стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим времени из-за стадий.
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что чисел уже поделены в наборов. Мы можем использовать битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть , и маркер использует бит, количество маркеров в каждом контейнере должно быть не более . В дальнейшем т.к. м.ч. должны влезать в контейнер. Каждый контейнер содержит блоков, каждое м.ч. может содержать блоков. Заметим, что мы используем неконсервативное преимущество в для использования леммы шесть. Поэтому мы предполагаем что м.ч. в каждом из которых блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит маркеров, то для каждого контейнера требуется бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется контейнеров то время необходимое для помещения м.ч. в их наборы потребуется времени.
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.
При таком помещении мы сразу сталкиваемся со следующей проблемой.
Рассмотрим число , которое является -ым в наборе . Рассмотрим блок (назовем его ), который является -ым м.ч. в . Когда мы используем вышеописанный метод перемещения нескольких следующих блоков (назовем это ) в , просто перемещен на позицию в наборе , но не обязательно на позицию (где расположен ). Если значение блока одинаково для всех чисел в , то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в помещен . Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди блоков это текущий блок. Таким образом после того как мы поместили эти блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных блоках.
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из элементов в наборы размера , то проблем не должно быть.
Собственно алгоритм:
Algorithm Sort(, , , , ..., )
это неконсервативное преимущество, -ые это входящие целые числа в наборе, которые надо отсортировать, это уровень рекурсии.
1)
тогда изучить размер набора. Если размер меньше или равен , то . Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. .
2)
От до
2.1) Упаковать -ый в часть из -ых номеров контейнеров, где содержит несколько непрерывных блоков, которые состоят из -ых битов и у которого текущий блок это самый крупный блок.
2.2) Вызвать Sort(, , , , ..., ). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в , показывающее на ткущий блок в нем, так же отправлено назад к .
2.3) Отправить
end.
Algorithm IterateSort Call Sort(, , , , ..., );
от 1 до 5
начало
Поместить в соответствующий набор с помощью bucket sort потому, что наборов около
Для каждого набора {}, если , вызвать Sort(, , )
конец
Время работы алгоритма , что доказывает лемму 2.