http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&user=Da1s60&feedformat=atomВикиконспекты - Вклад участника [ru]2024-03-28T12:23:31ZВклад участникаMediaWiki 1.30.0http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25343Сортировка Хана2012-06-12T16:45:32Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (<tex>0< e < 1</tex>) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества <tex>\{0, 1, \ldots, m - 1\}</tex> с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хеширование для всех чисел хранимых в контейнере. Для этого используется хеш функция для хеширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хеш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хеш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством <tex>\{0, \ldots, 2^b - 1\}<tex>, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хеш функцию <tex>h_{a}</tex> которая захеширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хеш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хеш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
<br />
<br />
Такая хеш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хеширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хеширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хешированных значений для каждого числа. После сортировки на хешированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, <tex>\ldots</tex>, <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
<br />
<br />
Пример:<br />
<br />
<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = <tex>\{1, 4, 6, 8, 9, 13, 14\}</tex>.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
<br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, <tex>\ldots</tex>, <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, <tex>\ldots</tex>, <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
<br />
<br />
Собственно алгоритм:<br />
<br />
<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, <tex>\ldots</tex>, <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
# Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
# Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>)<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, \ldots, m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
<br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, \ldots, S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, \ldots, T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, \ldots, a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, \ldots, a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, \ldots, b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хешируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хешированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хеш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хеш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хеш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хеш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хеш контейнер как число и сортируются эти хеш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
<br />
<br />
Хеш функция, которая используется, находится следующим образом. Будут хешироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, <tex>\ldots</tex> от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хеш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хеш функций.<br />
<br />
<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хешированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хешированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хеш контейнеры в один pool, сортируем следующим образом.<br />
<br />
<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хешированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хешированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хешированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2} \ldots t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хешированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}0^{j}t_{1, 2} \ldots t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, \ldots, h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}t_{1, 2}t_{2, 2} \ldots t_{1, h/ \log\log n}t_{2, h/ \log\log n} \ldots t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хеш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} \ldots t_{k, h/ \log\log n} k = 1, 2, \ldots, \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} \ldots t_{1, h/ \log\log n}0^{r}t_{2, 1} \ldots t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} \ldots t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} \ldots t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
# Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
# А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25342Сортировка Хана2012-06-12T16:44:47Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (<tex>0< e < 1</tex>) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества <tex>\{0, 1, \ldots, m - 1\}</tex> с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хеширование для всех чисел хранимых в контейнере. Для этого используется хеш функция для хеширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хеш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хеш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) \div 2^{b - s}</tex>.<br />
<br />
<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством <tex>\{0, \ldots, 2^b - 1\}<tex>, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хеш функцию <tex>h_{a}</tex> которая захеширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хеш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хеш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
<br />
<br />
Такая хеш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хеширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хеширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хешированных значений для каждого числа. После сортировки на хешированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, <tex>\ldots</tex>, <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
<br />
<br />
Пример:<br />
<br />
<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = <tex>\{1, 4, 6, 8, 9, 13, 14\}</tex>.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
<br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, <tex>\ldots</tex>, <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, <tex>\ldots</tex>, <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
<br />
<br />
Собственно алгоритм:<br />
<br />
<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, <tex>\ldots</tex>, <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
# Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
# Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>)<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, \ldots, m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
<br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, \ldots, S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, \ldots, T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, \ldots, a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, \ldots, a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, \ldots, b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хешируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хешированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хеш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хеш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хеш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хеш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хеш контейнер как число и сортируются эти хеш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
<br />
<br />
Хеш функция, которая используется, находится следующим образом. Будут хешироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, <tex>\ldots</tex> от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хеш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хеш функций.<br />
<br />
<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хешированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хешированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хеш контейнеры в один pool, сортируем следующим образом.<br />
<br />
<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хешированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хешированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хешированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2} \ldots t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хешированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}0^{j}t_{1, 2} \ldots t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, \ldots, h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}t_{1, 2}t_{2, 2} \ldots t_{1, h/ \log\log n}t_{2, h/ \log\log n} \ldots t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хеш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} \ldots t_{k, h/ \log\log n} k = 1, 2, \ldots, \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} \ldots t_{1, h/ \log\log n}0^{r}t_{2, 1} \ldots t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} \ldots t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} \ldots t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
# Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
# А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25341Сортировка Хана2012-06-12T16:43:29Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (<tex>0< e < 1</tex>) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества <tex>\{0, 1, \ldots, m - 1\}</tex> с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хеширование для всех чисел хранимых в контейнере. Для этого используется хеш функция для хеширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хеш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хеш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством <tex>\{0, \ldots, 2^b - 1\}<tex>, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хеш функцию <tex>h_{a}</tex> которая захеширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хеш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хеш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
<br />
<br />
Такая хеш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хеширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хеширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хешированных значений для каждого числа. После сортировки на хешированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, <tex>\ldots</tex>, <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
<br />
<br />
Пример:<br />
<br />
<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = <tex>\{1, 4, 6, 8, 9, 13, 14\}</tex>.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
<br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, <tex>\ldots</tex>, <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, <tex>\ldots</tex>, <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
<br />
<br />
Собственно алгоритм:<br />
<br />
<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, <tex>\ldots</tex>, <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
# Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
# Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>)<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, \ldots, m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
<br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, \ldots, S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, \ldots, T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, \ldots, a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, \ldots, a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, \ldots, b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хешируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хешированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хеш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хеш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хеш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хеш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хеш контейнер как число и сортируются эти хеш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
<br />
<br />
Хеш функция, которая используется, находится следующим образом. Будут хешироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, <tex>\ldots</tex> от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хеш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хеш функций.<br />
<br />
<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хешированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хешированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хеш контейнеры в один pool, сортируем следующим образом.<br />
<br />
<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хешированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хешированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хешированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2} \ldots t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хешированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}0^{j}t_{1, 2} \ldots t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, \ldots, h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}t_{1, 2}t_{2, 2} \ldots t_{1, h/ \log\log n}t_{2, h/ \log\log n} \ldots t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хеш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} \ldots t_{k, h/ \log\log n} k = 1, 2, \ldots, \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} \ldots t_{1, h/ \log\log n}0^{r}t_{2, 1} \ldots t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} \ldots t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} \ldots t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
# Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
# А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25292Сортировка Хана2012-06-12T14:08:24Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (<tex>0< e < 1</tex>) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества <tex>\{0, 1, \ldots, m - 1\}</tex> с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством <tex>\{0, \ldots, 2^b - 1\}<tex>, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
<br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, <tex>\ldots</tex>, <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
<br />
<br />
Пример:<br />
<br />
<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = <tex>\{1, 4, 6, 8, 9, 13, 14\}</tex>.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
<br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, <tex>\ldots</tex>, <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, <tex>\ldots</tex>, <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
<br />
<br />
Собственно алгоритм:<br />
<br />
<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, <tex>\ldots</tex>, <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
# Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
# Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>)<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, \ldots, m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
<br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, \ldots, S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, \ldots, T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, \ldots, a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, \ldots, a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, \ldots, b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, <tex>\ldots</tex> от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2} \ldots t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}0^{j}t_{1, 2} \ldots t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, \ldots, h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}t_{1, 2}t_{2, 2} \ldots t_{1, h/ \log\log n}t_{2, h/ \log\log n} \ldots t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} \ldots t_{k, h/ \log\log n} k = 1, 2, \ldots, \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} \ldots t_{1, h/ \log\log n}0^{r}t_{2, 1} \ldots t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} \ldots t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} \ldots t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
# Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
# А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25291Сортировка Хана2012-06-12T14:07:56Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (<tex>0< e < 1</tex>) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества <tex>\{0, 1, \ldots, m - 1\}</tex> с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^{b}) div 2^{b - s}</tex>.<br />
<br />
<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством <tex>\{0, \ldots, 2^b - 1\}<tex>, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
<br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, <tex>\ldots</tex>, <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
<br />
<br />
Пример:<br />
<br />
<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = <tex>\{1, 4, 6, 8, 9, 13, 14\}</tex>.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
<br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, <tex>\ldots</tex>, <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, <tex>\ldots</tex>, <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
<br />
<br />
Собственно алгоритм:<br />
<br />
<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, <tex>\ldots</tex>, <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
# Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
# Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>)<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, \ldots, m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
<br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, \ldots, S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, \ldots, T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, \ldots, a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, \ldots, a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, \ldots, b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, <tex>\ldots</tex> от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2} \ldots t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}0^{j}t_{1, 2} \ldots t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, \ldots, h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}t_{1, 2}t_{2, 2} \ldots t_{1, h/ \log\log n}t_{2, h/ \log\log n} \ldots t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} \ldots t_{k, h/ \log\log n} k = 1, 2, \ldots, \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} \ldots t_{1, h/ \log\log n}0^{r}t_{2, 1} \ldots t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} \ldots t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} \ldots t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
# Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
# А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25290Сортировка Хана2012-06-12T14:06:16Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (<tex>0< e < 1</tex>) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества <tex>\{0, 1, \ldots, m - 1\}</tex> с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством <tex>\{0, \ldots, 2^b - 1\}<tex>, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
<br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, <tex>\ldots</tex>, <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
<br />
<br />
Пример:<br />
<br />
<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = <tex>\{1, 4, 6, 8, 9, 13, 14\}</tex>.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
<br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, <tex>\ldots</tex>, <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, <tex>\ldots</tex>, <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
<br />
<br />
Собственно алгоритм:<br />
<br />
<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, <tex>\ldots</tex>, <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
# Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
# Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>)<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, \ldots, m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
<br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, \ldots, S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, \ldots, T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, \ldots, a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, \ldots, a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, \ldots, b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, <tex>\ldots</tex> от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2} \ldots t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}0^{j}t_{1, 2} \ldots t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, \ldots, h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}t_{1, 2}t_{2, 2} \ldots t_{1, h/ \log\log n}t_{2, h/ \log\log n} \ldots t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} \ldots t_{k, h/ \log\log n} k = 1, 2, \ldots, \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} \ldots t_{1, h/ \log\log n}0^{r}t_{2, 1} \ldots t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} \ldots t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} \ldots t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
# Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
# А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25289Обсуждение:Сортировка Хана2012-06-12T14:05:25Z<p>Da1s60: </p>
<hr />
<div>* <tex>log</tex> -> <tex>\log</tex><br />
<br />
готово<br />
<br />
* Сокращение Э.П.дерево выглядит очень странно, лучше ЭП-дерево<br />
<br />
готово<br />
<br />
* Если есть ещё какая-нибудь информация про это самое ЭП-дерево, добавить в ссылки<br />
<br />
готово<br />
<br />
* Раздел "необходимая информация" лучше переименовать в "определения", и вместо блоков с определениями сделать обычный список<br />
<br />
готово<br />
<br />
* Определение контейнера какое-то странное, и ещё вместо __int32 и __int64 писать "32-битные числа" и "64-битные числа"<br />
<br />
готово<br />
<br />
* "Если мы", "тогда мы" выглядит плохо. Лучше всего — писать обезличенно.<br />
<br />
готово<br />
<br />
постарался убрать все "Если мы", "тогда мы", надеюсь вышло более-менее обезличенно<br />
<br />
* Посмотреть на четвёртое определение: я там дописал, как в техе записывать всякие минимумы, принадлежность множеству и т.д. Использовать такие формулы вместо текстовых описаний.<br />
<br />
готово<br />
<br />
сделал где увидел<br />
<br />
* Вместо >= писать <tex>\ge</tex> (соответственно <tex>\le</tex> вместо <=)<br />
<br />
готово<br />
<br />
* Очень крупные абзацы в тексте, разбить на абзацы по-мельче<br />
<br />
на мой взгляд, в каждом абзаце представлена законченная мысль, поэтому не думаю, что их надо разделять<br />
<br />
для удобства я их разделил пустой строкой (чтобы не сливались в сплошной текст)<br />
<br />
* Вместо ручной нумерации использовать вики-разметку<br />
<br />
готово<br />
<br />
сделал где, по моему мнению, нужна была нумерация<br />
<br />
* Корень из n писать как <tex>\sqrt{n}</tex><br />
<br />
готово<br />
<br />
сделал везде, кроме одного заголовка, никак не получается выводить его как ссылку если писать через корешок<br />
<br />
* "Вывод" снести, он не нужен.<br />
<br />
готово<br />
<br />
--[[Участник:Андрей Шулаев|Андрей Шулаев]] 14:05, 12 июня 2012 (GST)<br />
<br />
Рекомендую кураторам обратить внимание на то, что в статье полно формул, записанных вот так: {0, 1, ..., <tex>m</tex> - 1} вместо <tex>\{0, 1, \ldots, m - 1\}</tex>.<br />
<br />
готово</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25288Сортировка Хана2012-06-12T14:04:48Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества <tex>\{0, 1, \ldots, m - 1\}</tex> с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством <tex>\{0, \ldots, 2^b - 1\}<tex>, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
<br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, <tex>\ldots</tex>, <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
<br />
<br />
Пример:<br />
<br />
<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = <tex>\{1, 4, 6, 8, 9, 13, 14\}</tex>.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
<br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, <tex>\ldots</tex>, <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, <tex>\ldots</tex>, <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
<br />
<br />
Собственно алгоритм:<br />
<br />
<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, <tex>\ldots</tex>, <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
# Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
# Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>)<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, \ldots, m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
<br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, \ldots, S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, \ldots, T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, \ldots, a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, \ldots, a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, \ldots, b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, <tex>\ldots</tex> от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2} \ldots t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}0^{j}t_{1, 2} \ldots t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, \ldots, h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}t_{1, 2}t_{2, 2} \ldots t_{1, h/ \log\log n}t_{2, h/ \log\log n} \ldots t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} \ldots t_{k, h/ \log\log n} k = 1, 2, \ldots, \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} \ldots t_{1, h/ \log\log n}0^{r}t_{2, 1} \ldots t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} \ldots t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} \ldots t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
# Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
# А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25286Сортировка Хана2012-06-12T14:03:50Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества <tex>\{0, 1, \ldots, m - 1\}</tex> с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством <tex>\{0, \ldots, 2^b - 1\}<tex>, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
<br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, .\ldots, <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
<br />
<br />
Пример:<br />
<br />
<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = <tex>\{1, 4, 6, 8, 9, 13, 14\}</tex>.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
<br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, <tex>\ldots</tex>, <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, <tex>\ldots</tex>, <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
<br />
<br />
Собственно алгоритм:<br />
<br />
<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, <tex>\ldots</tex>, <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, <tex>\ldots</tex>, <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
# Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
# Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>)<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, \ldots, m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
<br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, \ldots, S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, \ldots, T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, \ldots, a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < <tex>\ldots</tex> < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, \ldots, a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, \ldots, b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, \ldots, S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, <tex>\ldots</tex> от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < <tex>\ldots</tex> < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2} \ldots t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}0^{j}t_{1, 2} \ldots t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, \ldots, h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}t_{1, 2}t_{2, 2} \ldots t_{1, h/ \log\log n}t_{2, h/ \log\log n} \ldots t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} \ldots t_{k, h/ \log\log n} k = 1, 2, \ldots, \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} \ldots t_{1, h/ \log\log n}0^{r}t_{2, 1} \ldots t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} \ldots t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} \ldots t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
# Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
# А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25285Сортировка Хана2012-06-12T13:55:34Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества <tex>{0, 1, ..., m - 1}</tex> с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством <tex>{0, ..., 2^b - 1}<tex>, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
<br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
<br />
<br />
Пример:<br />
<br />
<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
<br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества <tex>{0, 1, ..., m - 1}</tex> в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
<br />
<br />
Собственно алгоритм:<br />
<br />
<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
# Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
# Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
<br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
# Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
# А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25269Сортировка Хана2012-06-12T13:17:46Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
<br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
<br />
<br />
Пример:<br />
<br />
<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
<br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
<br />
<br />
Собственно алгоритм:<br />
<br />
<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
# Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
# Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
<br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
# Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
# А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25265Обсуждение:Сортировка Хана2012-06-12T13:12:01Z<p>Da1s60: </p>
<hr />
<div>* <tex>log</tex> -> <tex>\log</tex><br />
<br />
готово<br />
<br />
* Сокращение Э.П.дерево выглядит очень странно, лучше ЭП-дерево<br />
<br />
готово<br />
<br />
* Если есть ещё какая-нибудь информация про это самое ЭП-дерево, добавить в ссылки<br />
<br />
готово<br />
<br />
* Раздел "необходимая информация" лучше переименовать в "определения", и вместо блоков с определениями сделать обычный список<br />
<br />
готово<br />
<br />
* Определение контейнера какое-то странное, и ещё вместо __int32 и __int64 писать "32-битные числа" и "64-битные числа"<br />
<br />
готово<br />
<br />
* "Если мы", "тогда мы" выглядит плохо. Лучше всего — писать обезличенно.<br />
<br />
готово<br />
<br />
постарался убрать все "Если мы", "тогда мы", надеюсь вышло более-менее обезличенно<br />
<br />
* Посмотреть на четвёртое определение: я там дописал, как в техе записывать всякие минимумы, принадлежность множеству и т.д. Использовать такие формулы вместо текстовых описаний.<br />
<br />
готово<br />
<br />
сделал где увидел<br />
<br />
* Вместо >= писать <tex>\ge</tex> (соответственно <tex>\le</tex> вместо <=)<br />
<br />
готово<br />
<br />
* Очень крупные абзацы в тексте, разбить на абзацы по-мельче<br />
<br />
на мой взгляд, в каждом абзаце представлена законченная мысль, поэтому не думаю, что их надо разделять<br />
<br />
для удобства я их разделил пустой строкой (чтобы не сливались в сплошной текст)<br />
<br />
* Вместо ручной нумерации использовать вики-разметку<br />
<br />
готово<br />
<br />
сделал где, по моему мнению, нужна была нумерация<br />
<br />
* Корень из n писать как <tex>\sqrt{n}</tex><br />
<br />
готово<br />
<br />
сделал везде, кроме одного заголовка, никак не получается выводить его как ссылку если писать через корешок<br />
<br />
* "Вывод" снести, он не нужен.<br />
<br />
готово<br />
<br />
--[[Участник:Андрей Шулаев|Андрей Шулаев]] 14:05, 12 июня 2012 (GST)</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25264Обсуждение:Сортировка Хана2012-06-12T13:10:53Z<p>Da1s60: </p>
<hr />
<div>* <tex>log</tex> -> <tex>\log</tex><br />
<br />
готово<br />
<br />
* Сокращение Э.П.дерево выглядит очень странно, лучше ЭП-дерево<br />
<br />
готово<br />
<br />
* Если есть ещё какая-нибудь информация про это самое ЭП-дерево, добавить в ссылки<br />
<br />
готово<br />
<br />
* Раздел "необходимая информация" лучше переименовать в "определения", и вместо блоков с определениями сделать обычный список<br />
<br />
готово<br />
<br />
* Определение контейнера какое-то странное, и ещё вместо __int32 и __int64 писать "32-битные числа" и "64-битные числа"<br />
<br />
готово<br />
<br />
* "Если мы", "тогда мы" выглядит плохо. Лучше всего — писать обезличенно.<br />
<br />
готово<br />
<br />
постарался убрать все "Если мы", "тогда мы", надеюсь вышло более-менее обезличенно<br />
<br />
* Посмотреть на четвёртое определение: я там дописал, как в техе записывать всякие минимумы, принадлежность множеству и т.д. Использовать такие формулы вместо текстовых описаний.<br />
<br />
готово<br />
<br />
сделал где увидел<br />
<br />
* Вместо >= писать <tex>\ge</tex> (соответственно <tex>\le</tex> вместо <=)<br />
<br />
готово<br />
<br />
* Очень крупные абзацы в тексте, разбить на абзацы по-мельче<br />
<br />
на мой взгляд, в каждом абзаце представлена законченная мысль, поэтому не думаю, что их надо разделять<br />
<br />
для удобства я их разделил пустой строкой (чтобы не сливались в сплошной текст)<br />
<br />
* Вместо ручной нумерации использовать вики-разметку<br />
<br />
готово<br />
<br />
сделал где, по моему мнению, нужна была нумерация<br />
<br />
* Корень из n писать как <tex>\sqrt{n}</tex><br />
<br />
готово<br />
<br />
сделал везде, кроме одного заголовка, никак не получается выводить его как ссылку если писать как <tex>\ sqrt{n}</tex>.<br />
<br />
* "Вывод" снести, он не нужен.<br />
<br />
готово<br />
<br />
--[[Участник:Андрей Шулаев|Андрей Шулаев]] 14:05, 12 июня 2012 (GST)</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25263Сортировка Хана2012-06-12T13:08:05Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
<br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
<br />
<br />
Пример:<br />
<br />
<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
<br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
<br />
<br />
Собственно алгоритм:<br />
<br />
<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
# Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
# Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
<br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25261Сортировка Хана2012-06-12T13:05:33Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
# Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
# Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25259Обсуждение:Сортировка Хана2012-06-12T13:02:41Z<p>Da1s60: </p>
<hr />
<div>* <tex>log</tex> -> <tex>\log</tex><br />
<br />
готово<br />
<br />
* Сокращение Э.П.дерево выглядит очень странно, лучше ЭП-дерево<br />
<br />
готово<br />
<br />
* Если есть ещё какая-нибудь информация про это самое ЭП-дерево, добавить в ссылки<br />
<br />
готово<br />
<br />
* Раздел "необходимая информация" лучше переименовать в "определения", и вместо блоков с определениями сделать обычный список<br />
<br />
готово<br />
<br />
* Определение контейнера какое-то странное, и ещё вместо __int32 и __int64 писать "32-битные числа" и "64-битные числа"<br />
<br />
готово<br />
<br />
* "Если мы", "тогда мы" выглядит плохо. Лучше всего — писать обезличенно.<br />
<br />
готово<br />
<br />
постарался убрать все "Если мы", "тогда мы", надеюсь вышло более-менее обезличенно<br />
<br />
* Посмотреть на четвёртое определение: я там дописал, как в техе записывать всякие минимумы, принадлежность множеству и т.д. Использовать такие формулы вместо текстовых описаний.<br />
<br />
готово<br />
<br />
сделал где увидел<br />
<br />
* Вместо >= писать <tex>\ge</tex> (соответственно <tex>\le</tex> вместо <=)<br />
<br />
готово<br />
<br />
* Очень крупные абзацы в тексте, разбить на абзацы по-мельче<br />
* Вместо ручной нумерации использовать вики-разметку<br />
<br />
готово<br />
<br />
сделал где, по моему мнению, нужна была нумерация<br />
<br />
* Корень из n писать как <tex>\sqrt{n}</tex><br />
<br />
готово<br />
<br />
сделал везде, кроме одного заголовка, никак не получается выводить его как ссылку если писать как <tex>\ sqrt{n}</tex>.<br />
<br />
* "Вывод" снести, он не нужен.<br />
<br />
готово<br />
<br />
--[[Участник:Андрей Шулаев|Андрей Шулаев]] 14:05, 12 июня 2012 (GST)</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25258Сортировка Хана2012-06-12T13:01:11Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
А. Андерссон. Fast deterministic sorting and searching in linear space. Proc. 1996 IEEE Symp. on Foundations of Computer Science. 135-141(1996)<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25256Сортировка Хана2012-06-12T12:55:15Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
# <tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
# От <tex>u = 1</tex> до <tex>k</tex><br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25255Сортировка Хана2012-06-12T12:54:08Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
#<br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
#<br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
## Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
## Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
## Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25253Сортировка Хана2012-06-12T12:49:50Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если сортируются целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда сортировка происходит с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> получаем хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, спокойно применяется <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Рассматривается, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Необходимо разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. используется signature sorting, до того как делать вышеописанное разделение, необходимо поделить биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Так же делим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> берутся все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые берутся для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом преобразуется начальная задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Делим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как возможно делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, используем bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что используется <tex>(\log n)/2</tex> бит на контейнер понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом берутся <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом все числа отсортированы.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> сортировка <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров произойдет за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Перемещаем каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <tex>g \log\log n</tex> контейнеров, упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex>, упаковывая <tex>\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Предполагая, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Пренебрегая временем, потраченным на на эту упаковку, считается, что она бесплатна. По третьей лемме находим медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда убираем <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Используем <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому предполагается, что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. используется маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении сразу возникает следующая проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, помещаются последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того, как помещены эти <tex>k \log e</tex> блоков в набор, удаляется изначальный текущий блок, потому, что известно, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку сортируется набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} предполагается, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Далее всегда считается, что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, вставляется не один элемент за раз, а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз. Алгоритм полностью опускает все <tex>d^2</tex> чисел на один уровень. В корне опускаются <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как опустились все числа на следующий уровень и они успешно разделились на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем, берутся <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> и за раз и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь числа опускаются дальше в ЭП-дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Имеется <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень опускаются <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, то есть возможность использовать лемму 2 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается ниже. Для этого используется линейная техника многократного деления (multi-dividing technique), чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и хотется использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, хэшируются числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом), чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения, два контейнера объединяются в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассматриваются каждый хэш контейнер как число и сортируются эти хэш контейнеры за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом получилось дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как повторится вышеописанный процесс <tex>g</tex> раз, получится неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время, как потрачено только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которая используется, находится следующим образом. Будут хэшироватся сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число, получится <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то, получится не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагается, что хэшированные значения для каждого контейнера упаковались в <tex>(2 \log n)/(c \log\log n)</tex> бит. Есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Комбинируя все хэш контейнеры в один pool, сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
# Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
# Поместить все A[j] в A[j].set<br />
<br />
Таким образом заполняются все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит получаем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора собираются все его поднаборы в подзадачах в один набор. Затем используя лемму два, делается разделение. Так как получено неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и работа происходит на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
В итоге разделились <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть, получилось, что <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора делается следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. Ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25238Сортировка Хана2012-06-12T12:12:50Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект, в которым мы храним наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(\log n)/2</tex> бит на контейнер нам понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>g \log\log n</tex> контейнеров мы сначала упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>\log\log n</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Мы так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Мы делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>k \log e</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень ЭП-дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем ЭП-дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то мы получим не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2 \log n)/(c \log\log n)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и мы работаем на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Мы используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге мы используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25235Сортировка Хана2012-06-12T12:09:58Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <br />
<br />
<tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(\log n)/2</tex> бит на контейнер нам понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>g \log\log n</tex> контейнеров мы сначала упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>\log\log n</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Мы так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Мы делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>k \log e</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень ЭП-дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем ЭП-дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то мы получим не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2 \log n)/(c \log\log n)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и мы работаем на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Мы используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге мы используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25234Сортировка Хана2012-06-12T12:09:26Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Определения== <br />
# Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
# Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
# Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
# Для множества <tex>S</tex> определим<br />
<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex> <tex>\max(S) = \max\limits_{a \in S} a</tex><br />
<br />
Набор <tex>S1</tex> < <tex>S2</tex> если <tex>\max(S1) \le \min(S2)</tex><br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(\log n)/2</tex> бит на контейнер нам понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>g \log\log n</tex> контейнеров мы сначала упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>\log\log n</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Мы так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Мы делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>k \log e</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень ЭП-дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем ЭП-дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то мы получим не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2 \log n)/(c \log\log n)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и мы работаем на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Мы используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге мы используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25229Сортировка Хана2012-06-12T11:57:06Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в ЭП-дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
ЭП-дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) ЭП-поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое ЭП-поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex><br />
<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <tex>\le</tex> min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(\log n)/2</tex> бит на контейнер нам понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>g \log\log n</tex> контейнеров мы сначала упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>\log\log n</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Мы так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Мы делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>k \log e</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое ЭП-дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень ЭП-дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем ЭП-дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному ЭП-дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из ЭП-дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то мы получим не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2 \log n)/(c \log\log n)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и мы работаем на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в ЭП-дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Мы используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге мы используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25226Сортировка Хана2012-06-12T11:54:44Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> <tex>\ge</tex> 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex><br />
<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <tex>\le</tex> min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> <tex>\ge</tex> <tex>s</tex> <tex>\ge</tex> 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> <tex>\ge</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <tex>\le</tex> t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(\log n)/2</tex> бит на контейнер нам понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>g \log\log n</tex> контейнеров мы сначала упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>\log\log n</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Мы так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Мы делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>k \log e</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <tex>\le</tex> 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то мы получим не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2 \log n)/(c \log\log n)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и мы работаем на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в Э.П.дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Мы используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге мы используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25224Сортировка Хана2012-06-12T11:52:06Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> \ge 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex><br />
<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) \le min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b \ge 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> \ge <tex>s</tex> \ge 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> \ge <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) \le t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(\log n)/2</tex> бит на контейнер нам понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>g \log\log n</tex> контейнеров мы сначала упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>\log\log n</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g \le \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Мы так же предполагаем, что <tex>\log m \ge \log n \log\log n</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Мы делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex>\le <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>k \log e</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в \le 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt \ge n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то мы получим не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2 \log n)/(c \log\log n)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и мы работаем на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>\log m \ge \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в Э.П.дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Мы используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге мы используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25221Обсуждение:Сортировка Хана2012-06-12T11:50:36Z<p>Da1s60: </p>
<hr />
<div>* <tex>log</tex> -> <tex>\log</tex><br />
<br />
готово<br />
* Сокращение Э.П.дерево выглядит очень странно, лучше ЭП-дерево<br />
* Если есть ещё какая-нибудь информация про это самое ЭП-дерево, добавить в ссылки<br />
* Раздел "необходимая информация" лучше переименовать в "определения", и вместо блоков с определениями сделать обычный список<br />
* Определение контейнера какое-то странное, и ещё вместо __int32 и __int64 писать "32-битные числа" и "64-битные числа"<br />
* "Если мы", "тогда мы" выглядит плохо. Лучше всего — писать обезличенно.<br />
* Посмотреть на четвёртое определение: я там дописал, как в техе записывать всякие минимумы, принадлежность множеству и т.д. Использовать такие формулы вместо текстовых описаний.<br />
* Вместо >= писать <tex>\ge</tex> (соответственно <tex>\le</tex> вместо <=)<br />
* Очень крупные абзацы в тексте, разбить на абзацы по-мельче<br />
* Вместо ручной нумерации использовать вики-разметку<br />
* Корень из n писать как <tex>\sqrt{n}</tex><br />
<br />
готово<br />
<br />
сделал везде, кроме одного заголовка, никак не получается выводить его как ссылку если писать как <tex>\ sqrt{n}</tex>.<br />
* "Вывод" снести, он не нужен.<br />
<br />
готово<br />
--[[Участник:Андрей Шулаев|Андрей Шулаев]] 14:05, 12 июня 2012 (GST)</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25220Обсуждение:Сортировка Хана2012-06-12T11:49:15Z<p>Da1s60: </p>
<hr />
<div>* <tex>log</tex> -> <tex>\log</tex><br />
готово<br />
* Сокращение Э.П.дерево выглядит очень странно, лучше ЭП-дерево<br />
* Если есть ещё какая-нибудь информация про это самое ЭП-дерево, добавить в ссылки<br />
* Раздел "необходимая информация" лучше переименовать в "определения", и вместо блоков с определениями сделать обычный список<br />
* Определение контейнера какое-то странное, и ещё вместо __int32 и __int64 писать "32-битные числа" и "64-битные числа"<br />
* "Если мы", "тогда мы" выглядит плохо. Лучше всего — писать обезличенно.<br />
* Посмотреть на четвёртое определение: я там дописал, как в техе записывать всякие минимумы, принадлежность множеству и т.д. Использовать такие формулы вместо текстовых описаний.<br />
* Вместо >= писать <tex>\ge</tex> (соответственно <tex>\le</tex> вместо <=)<br />
* Очень крупные абзацы в тексте, разбить на абзацы по-мельче<br />
* Вместо ручной нумерации использовать вики-разметку<br />
* Корень из n писать как <tex>\sqrt{n}</tex><br />
готово<br />
сделал везде, кроме одного заголовка, никак не получается выводить его как ссылку если писать как <tex>sqrt{n}</tex>.<br />
* "Вывод" снести, он не нужен.<br />
готово<br />
--[[Участник:Андрей Шулаев|Андрей Шулаев]] 14:05, 12 июня 2012 (GST)</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25217Сортировка Хана2012-06-12T11:47:13Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex><br />
<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(\log n)/2</tex> бит на контейнер нам понадобится <tex>\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt{n}(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>g \log\log n</tex> контейнеров мы сначала упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>\log\log n</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g <= \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Мы так же предполагаем, что <tex>\log m >= \log n \log\log n</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Мы делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>k \log e</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то мы получим не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2 \log n)/(c \log\log n)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и мы работаем на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>\log m >= \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в Э.П.дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Мы используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге мы используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5:%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25216Обсуждение:Сортировка Хана2012-06-12T11:44:51Z<p>Da1s60: </p>
<hr />
<div>* <tex>log</tex> -> <tex>\log</tex><br />
готово<br />
* Сокращение Э.П.дерево выглядит очень странно, лучше ЭП-дерево<br />
* Если есть ещё какая-нибудь информация про это самое ЭП-дерево, добавить в ссылки<br />
* Раздел "необходимая информация" лучше переименовать в "определения", и вместо блоков с определениями сделать обычный список<br />
* Определение контейнера какое-то странное, и ещё вместо __int32 и __int64 писать "32-битные числа" и "64-битные числа"<br />
* "Если мы", "тогда мы" выглядит плохо. Лучше всего — писать обезличенно.<br />
* Посмотреть на четвёртое определение: я там дописал, как в техе записывать всякие минимумы, принадлежность множеству и т.д. Использовать такие формулы вместо текстовых описаний.<br />
* Вместо >= писать <tex>\ge</tex> (соответственно <tex>\le</tex> вместо <=)<br />
* Очень крупные абзацы в тексте, разбить на абзацы по-мельче<br />
* Вместо ручной нумерации использовать вики-разметку<br />
* Корень из n писать как <tex>\sqrt{n}</tex><br />
* "Вывод" снести, он не нужен.<br />
--[[Участник:Андрей Шулаев|Андрей Шулаев]] 14:05, 12 июня 2012 (GST)</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25215Сортировка Хана2012-06-12T11:43:57Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex><br />
<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(\log n)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>g \log\log n</tex> контейнеров мы сначала упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>\log\log n</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g <= \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в <tex>sqrt(n)</tex> наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Мы так же предполагаем, что <tex>\log m >= \log n \log\log n</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Мы делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>k \log e</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием <tex>O(n \log\log n)</tex> времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то мы получим не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2 \log n)/(c \log\log n)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и мы работаем на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>\log m >= \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в Э.П.дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Мы используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге мы используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Вывод==<br />
Таким образом имеем:<br />
{{Теорема<br />
|id=th1. <br />
|statement=<br />
<tex>n</tex> целых чисел могут быть отсортированы за время <tex>O(n \log\log n)</tex> и линейную память.<br />
}}<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25214Сортировка Хана2012-06-12T11:42:20Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n \log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>k \log (m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex><br />
<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2 \log n</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>\log m</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>\log (m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2h \log n</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex> \log (m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>\log m</tex> бит в несколько задач на разделение с числами в <tex>\log (m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(n \log\log n/ \log k)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>k \log\log n</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(n \log g/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(\log n)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(\log n)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(\log n)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (\log n)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(g \log g)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g) \log g)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O( \log n)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g) \log\log n)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex> \log m > \log n</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((n \log\log n)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>( \log n)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>\log m \log\log n > \log n</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(\log m)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(\log n)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>\log m \log\log n</tex> бит всего <tex>\log m</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>g \log\log n</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>g \log\log n</tex> контейнеров мы сначала упаковываем <tex>g \log\log n</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>\log\log n</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(g \log\log n)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>g \log\log n</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>\log m \log\log n</tex> и только <tex>\log m</tex> бит используется для упаковки <tex>g <= \log n</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>k \log\log n \log m</tex> бит и хранит число в <tex>\log m</tex> бит. Поэтому неконсервативное преимущество <tex>k \log \log n</tex>. Мы так же предполагаем, что <tex>\log m >= \log n \log\log n</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(n \log\log n)</tex> и линейную память. Мы делим <tex>\log m</tex> бит, используемых для представления каждого числа, в <tex>\log n</tex> блоков. Таким образом каждый блок содержит как минимум <tex>\log\log n</tex> бит. <tex>i</tex>-ый блок содержит с <tex>i \log m/ \log n</tex>-ого по <tex>((i + 1) \log m/ \log n - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2 \log n</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>\log m/ \log n</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/ \log n</tex> контейнеров с <tex>\log n</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/ \log n)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>\log m/ \log n</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>\log n</tex> блоков, для каждого числа потребуется не более <tex>\log n</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/ \log n</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2 \log n</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>\log e</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>\log n/2</tex>, и маркер использует <tex>\log e</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>\log n/(2\log e)</tex>. В дальнейшем т.к. <tex>g = \log n/(2 \log e)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <tex>O(k \log n/g) = O(k \log e)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>\log\log n</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>\log n/(2 \log e)</tex> м.ч. в каждом из которых <tex>k \log e</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>\log e</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>\log n/(2 \log e)</tex> маркеров, то для каждого контейнера требуется <tex>(\log n)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((n \log e)/ \log n)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((n \log e)/ \log n)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>k \log e</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>k \log e</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>k \log e</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>k \log e</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>k \log\log n</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>k \log\log n</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>k \log\log n</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>k \log\log n</tex>, <tex>\log_{k}((\log n)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(n \log\log n/ \log k)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(n \log\log n) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(\log (m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(n \log\log n)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = \log n/(c \log p)</tex> для константы <tex>c > 1</tex>. <tex>h/ \log\log n \log p</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(\log n)/(c \log\log n)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/ \log\log n</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/ \log\log n</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2 \log n)/(c \log\log n)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/ \log\log n</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2 \log n)/(c \log\log n)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>\log\log n/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/ \log\log n</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>\log\log n/h</tex>-ые, <tex>(\log\log n/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(\log\log n/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(\log\log n/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(\log\log n/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < \log n</tex> то мы получим не более <tex>\log n</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2 \log n)/(c \log\log n)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2 \log n)/(c \log\log n)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2 \log n)/(c \log\log n)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/ \log\log n)^g</tex> и мы работаем на уровнях не ниже чем <tex>2 \log\log\log n</tex>, то алгоритм занимает <tex>O(qt \log\log n/(g(\log h - \log\log\log n) - \log\log\log n)) = O(\log\log n)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>\log m >= \log\log\log n</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/ \log\log n</tex> хэшированных значений (сегментов) в себе на уровне <tex>\log h</tex> в Э.П.дереве. Полное число хэшированных бит в контейнере <tex>(2 \log n)(c \log\log n)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/ \log\log n}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем <tex>\log\log n</tex> контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{\log\log n, 1}0^{j}t_{1, 2}...t_{\log\log n, h/ \log\log n}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/ \log\log n</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(\log\log n)</tex> шагов, чтобы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/ \log\log n}t_{1, 1}t_{2, 1} ... t_{\log\log n, 1}t_{1, 2}t_{2, 2} ... t_{1, h/ \log\log n}t_{2, h/ \log\log n} ... t_{\log\log n, h/ \log\log n}</tex>. Теперь упакованные хэш биты занимают <tex>2 \log n/c</tex> бит. Мы используем <tex>O(\log\log n)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>\log\log n</tex> контейнеров <tex>w_{3, k} = 0^{jh/ \log\log n}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/ \log\log n} k = 1, 2, ..., \log\log n</tex>. Затем используя <tex>O(\log\log n)</tex> времени упаковываем эти <tex>\log\log n</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/ \log\log n}0^{r}t_{2, 1} ... t_{\log\log n, h/ \log\log n}</tex>. Затем используя <tex>O(\log\log n)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/ \log\log n}t_{2, 1}t_{2, 2} ... t_{\log\log n, h/ \log\log n}</tex>. В итоге мы используем <tex>O(\log\log n)</tex> времени для упаковки <tex>\log\log n</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Вывод==<br />
Таким образом имеем:<br />
{{Теорема<br />
|id=th1. <br />
|statement=<br />
<tex>n</tex> целых чисел могут быть отсортированы за время <tex>O(n \log\log n)</tex> и линейную память.<br />
}}<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(n \log\log n) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25210Сортировка Хана2012-06-12T11:22:05Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(n\log\log n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n\log\logn)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n\log\logn)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
<tex>\min(S) = \min\limits_{a \in S} a</tex><br />
<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(log(m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(nloglogn)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = logn/(clogp)</tex> для константы <tex>c > 1</tex>. <tex>h/loglognlogp</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(logn)/(cloglogn)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/loglogn</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/loglogn</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2logn)/(cloglogn)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/loglogn</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2logn)/(cloglogn)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>loglogn/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/loglogn</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>loglogn/h</tex>-ые, <tex>(loglogn/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(loglogn/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(loglogn/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(loglogn/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < logn</tex> то мы получим не более <tex>logn</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2logn)/(cloglogn)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2logn)/(cloglogn)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2logn)/(cloglogn)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/loglogn)^g</tex> и мы работаем на уровнях не ниже чем <tex>2logloglogn</tex>, то алгоритм занимает <tex>O(qtloglogn/(g(logh - logloglogn) -logloglogn)) = O(loglogn)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>logm >= logloglogn</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/loglogn</tex> хэшированных значений (сегментов) в себе на уровне <tex>logh</tex> в Э.П.дереве. Полное число хэшированных бит в контейнере <tex>(2logn)(cloglogn)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/loglogn}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем loglogn контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{loglogn, 1}0^{j}t_{1, 2}...t_{loglogn, h/loglogn}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/loglogn</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(loglogn)</tex> шагов, чтбы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/loglogn}t_{1, 1}t_{2, 1} ... t_{loglogn, 1}t_{1, 2}t_{2, 2} ... t_{1, h/loglogn}t_{2, h/loglogn} ... t_{loglogn, h/loglogn}</tex>. Теперь упакованные хэш биты занимают <tex>2logn/c</tex> бит. Мы используем <tex>O(loglogn)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>loglogn</tex> контейнеров <tex>w_{3, k} = 0^{jh/loglogn}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/loglogn} k = 1, 2, ..., loglogn</tex>. Затем используя <tex>O(loglogn)</tex> времени упаковываем эти <tex>loglogn</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/loglogn}0^{r}t_{2, 1} ... t_{loglogn, h/loglogn}</tex>. Затем используя <tex>O(loglogn)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/loglogn}t_{2, 1}t_{2, 2} ... t_{loglogn, h/loglogn}</tex>. В итоге мы используем <tex>O(loglogn)</tex> времени для упаковки <tex>loglogn</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Вывод==<br />
Таким образом имеем:<br />
{{Теорема<br />
|id=th1. <br />
|statement=<br />
<tex>n</tex> целых чисел могут быть отсортированы за время <tex>O(nloglogn)</tex> и линейную память.<br />
}}<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(nloglogn) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25155Сортировка Хана2012-06-12T04:12:56Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(log(m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(nloglogn)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = logn/(clogp)</tex> для константы <tex>c > 1</tex>. <tex>h/loglognlogp</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(logn)/(cloglogn)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/loglogn</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/loglogn</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2logn)/(cloglogn)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/loglogn</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2logn)/(cloglogn)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>loglogn/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/loglogn</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>loglogn/h</tex>-ые, <tex>(loglogn/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(loglogn/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(loglogn/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(loglogn/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < logn</tex> то мы получим не более <tex>logn</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2logn)/(cloglogn)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2logn)/(cloglogn)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2logn)/(cloglogn)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/loglogn)^g</tex> и мы работаем на уровнях не ниже чем <tex>2logloglogn</tex>, то алгоритм занимает <tex>O(qtloglogn/(g(logh - logloglogn) -logloglogn)) = O(loglogn)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>logm >= logloglogn</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/loglogn</tex> хэшированных значений (сегментов) в себе на уровне <tex>logh</tex> в Э.П.дереве. Полное число хэшированных бит в контейнере <tex>(2logn)(cloglogn)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/loglogn}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем loglogn контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{loglogn, 1}0^{j}t_{1, 2}...t_{loglogn, h/loglogn}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/loglogn</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(loglogn)</tex> шагов, чтбы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/loglogn}t_{1, 1}t_{2, 1} ... t_{loglogn, 1}t_{1, 2}t_{2, 2} ... t_{1, h/loglogn}t_{2, h/loglogn} ... t_{loglogn, h/loglogn}</tex>. Теперь упакованные хэш биты занимают <tex>2logn/c</tex> бит. Мы используем <tex>O(loglogn)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>loglogn</tex> контейнеров <tex>w_{3, k} = 0^{jh/loglogn}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/loglogn} k = 1, 2, ..., loglogn</tex>. Затем используя <tex>O(loglogn)</tex> времени упаковываем эти <tex>loglogn</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/loglogn}0^{r}t_{2, 1} ... t_{loglogn, h/loglogn}</tex>. Затем используя <tex>O(loglogn)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/loglogn}t_{2, 1}t_{2, 2} ... t_{loglogn, h/loglogn}</tex>. В итоге мы используем <tex>O(loglogn)</tex> времени для упаковки <tex>loglogn</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Вывод==<br />
Таким образом имеем:<br />
{{Теорема<br />
|id=th1. <br />
|statement=<br />
<tex>n</tex> целых чисел могут быть отсортированы за время <tex>O(nloglogn)</tex> и линейную память.<br />
}}<br />
<br />
==Литераура==<br />
Deterministic Sorting in O(nloglogn) Time and Linear Space. Yijie Han.<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25154Сортировка Хана2012-06-12T04:10:37Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(log(m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(nloglogn)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = logn/(clogp)</tex> для константы <tex>c > 1</tex>. <tex>h/loglognlogp</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(logn)/(cloglogn)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/loglogn</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/loglogn</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2logn)/(cloglogn)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/loglogn</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2logn)/(cloglogn)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>loglogn/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/loglogn</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>loglogn/h</tex>-ые, <tex>(loglogn/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(loglogn/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(loglogn/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(loglogn/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < logn</tex> то мы получим не более <tex>logn</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2logn)/(cloglogn)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2logn)/(cloglogn)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2logn)/(cloglogn)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/loglogn)^g</tex> и мы работаем на уровнях не ниже чем <tex>2logloglogn</tex>, то алгоритм занимает <tex>O(qtloglogn/(g(logh - logloglogn) -logloglogn)) = O(loglogn)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>logm >= logloglogn</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/loglogn</tex> хэшированных значений (сегментов) в себе на уровне <tex>logh</tex> в Э.П.дереве. Полное число хэшированных бит в контейнере <tex>(2logn)(cloglogn)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/loglogn}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем loglogn контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{loglogn, 1}0^{j}t_{1, 2}...t_{loglogn, h/loglogn}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/loglogn</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(loglogn)</tex> шагов, чтбы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/loglogn}t_{1, 1}t_{2, 1} ... t_{loglogn, 1}t_{1, 2}t_{2, 2} ... t_{1, h/loglogn}t_{2, h/loglogn} ... t_{loglogn, h/loglogn}</tex>. Теперь упакованные хэш биты занимают <tex>2logn/c</tex> бит. Мы используем <tex>O(loglogn)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>loglogn</tex> контейнеров <tex>w_{3, k} = 0^{jh/loglogn}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/loglogn} k = 1, 2, ..., loglogn</tex>. Затем используя <tex>O(loglogn)</tex> времени упаковываем эти <tex>loglogn</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/loglogn}0^{r}t_{2, 1} ... t_{loglogn, h/loglogn}</tex>. Затем используя <tex>O(loglogn)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/loglogn}t_{2, 1}t_{2, 2} ... t_{loglogn, h/loglogn}</tex>. В итоге мы используем <tex>O(loglogn)</tex> времени для упаковки <tex>loglogn</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Вывод==<br />
Таким образом имеем:<br />
{{Теорема<br />
|id=th1. <br />
|statement=<br />
<tex>n</tex> целых чисел могут быть отсортированы за время <tex>O(nloglogn)</tex> и линейную память.<br />
}}<br />
<br />
[[Категория: Дискретная математика и алгоритмы]]<br />
<br />
[[Категория: Сортировки]]</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25153Сортировка Хана2012-06-12T04:08:20Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(log(m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(nloglogn)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = logn/(clogp)</tex> для константы <tex>c > 1</tex>. <tex>h/loglognlogp</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(logn)/(cloglogn)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/loglogn</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/loglogn</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2logn)/(cloglogn)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/loglogn</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2logn)/(cloglogn)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>loglogn/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/loglogn</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>loglogn/h</tex>-ые, <tex>(loglogn/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(loglogn/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(loglogn/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(loglogn/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < logn</tex> то мы получим не более <tex>logn</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2logn)/(cloglogn)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2logn)/(cloglogn)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2logn)/(cloglogn)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/loglogn)^g</tex> и мы работаем на уровнях не ниже чем <tex>2logloglogn</tex>, то алгоритм занимает <tex>O(qtloglogn/(g(logh - logloglogn) -logloglogn)) = O(loglogn)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>logm >= logloglogn</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/loglogn</tex> хэшированных значений (сегментов) в себе на уровне <tex>logh</tex> в Э.П.дереве. Полное число хэшированных бит в контейнере <tex>(2logn)(cloglogn)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/loglogn}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем loglogn контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{loglogn, 1}0^{j}t_{1, 2}...t_{loglogn, h/loglogn}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/loglogn</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(loglogn)</tex> шагов, чтбы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/loglogn}t_{1, 1}t_{2, 1} ... t_{loglogn, 1}t_{1, 2}t_{2, 2} ... t_{1, h/loglogn}t_{2, h/loglogn} ... t_{loglogn, h/loglogn}</tex>. Теперь упакованные хэш биты занимают <tex>2logn/c</tex> бит. Мы используем <tex>O(loglogn)</tex> времени чтобы распаковать <tex>w_{2}</tex> в <tex>loglogn</tex> контейнеров <tex>w_{3, k} = 0^{jh/loglogn}0^{r}t_{k, 1}O^{r}t_{k, 2} ... t_{k, h/loglogn} k = 1, 2, ..., loglogn</tex>. Затем используя <tex>O(loglogn)</tex> времени упаковываем эти <tex>loglogn</tex> контейнеров в один <tex>w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} ... t_{1, h/loglogn}0^{r}t_{2, 1} ... t_{loglogn, h/loglogn}</tex>. Затем используя <tex>O(loglogn)</tex> шагов упаковать <tex>w_{4}</tex> в <tex>w_{5} = 0^{s}t_{1, 1}t_{1, 2} ... t_{1, h/loglogn}t_{2, 1}t_{2, 2} ... t_{loglogn, h/loglogn}</tex>. В итоге мы используем <tex>O(loglogn)</tex> времени для упаковки <tex>loglogn</tex> контейнеров. Считаем что время потраченное на одно слово {{---}} константа.<br />
<br />
==Вывод==<br />
Таким образом имеем:<br />
{{Теорема<br />
|id=th1. <br />
|statement=<br />
<tex>n</tex> целых чисел могут быть отсортированы за время <tex>O(nloglogn)</tex> и линейную память.<br />
}}</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25152Сортировка Хана2012-06-12T03:52:03Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(log(m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(nloglogn)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = logn/(clogp)</tex> для константы <tex>c > 1</tex>. <tex>h/loglognlogp</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(logn)/(cloglogn)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/loglogn</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/loglogn</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2logn)/(cloglogn)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/loglogn</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые мы забиваем нулями. Сначала упаковываем все сегменты в <tex>(2logn)/(cloglogn)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>loglogn/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/loglogn</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>loglogn/h</tex>-ые, <tex>(loglogn/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(loglogn/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(loglogn/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(loglogn/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < logn</tex> то мы получим не более <tex>logn</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2logn)/(cloglogn)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2logn)/(cloglogn)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2logn)/(cloglogn)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/loglogn)^g</tex> и мы работаем на уровнях не ниже чем <tex>2logloglogn</tex>, то алгоритм занимает <tex>O(qtloglogn/(g(logh - logloglogn) -logloglogn)) = O(loglogn)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.<br />
<br />
Теперь рассмотрим проблему упаковки, которую решим следующим образом. Будем считать что число бит в контейнере <tex>logm >= logloglogn</tex>, потому, что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <tex>h/loglogn</tex> хэшированных значений (сегментов) в себе на уровне <tex>logh</tex> в Э.П.дереве. Полное число хэшированных бит в контейнере <tex>(2logn)(cloglogn)</tex> бит. Хотя хэшированны биты в контейнере выглядят как <tex>0^{i}t_{1}0^{i}t_{2}...t_{h/loglogn}</tex>, где <tex>t_{k}</tex>-ые это хэшированные биты, а нули это просто нули. Сначала упаковываем loglogn контейнеров в один и получаем <tex>w_{1} = 0^{j}t_{1, 1}t_{2, 1}...t_{loglogn, 1}0^{j}t_{1, 2}...t_{loglogn, h/loglogn}</tex> где <tex>t_{i, k}</tex>: <tex>k = 1, 2, ..., h/loglogn</tex> из <tex>i</tex>-ого контейнера. мы ипользуем <tex>O(loglogn)</tex> шагов, чтбы упаковать <tex>w_{1}</tex> в <tex>w_{2} = 0^{jh/loglogn}t_{1, 1}t_{2, 1} ... t_{loglogn, 1}t_{1, 2}t_{2, 2} ... t_{1, h/loglogn}t_{2, h/loglogn} ... t_{loglogn, h/loglogn}</tex>. Теперь упакованные хэш биты занимают <tex>2logn/c</tex> бит. Мы используем <tex>O(loglogn)</tex> времени</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25151Сортировка Хана2012-06-12T03:31:56Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(log(m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(nloglogn)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = logn/(clogp)</tex> для константы <tex>c > 1</tex>. <tex>h/loglognlogp</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(logn)/(cloglogn)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/loglogn</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/loglogn</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2logn)/(cloglogn)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/loglogn</tex> бит в каждом контейнере. Сначала упаковываем все сегменты в <tex>(2logn)/(cloglogn)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>loglogn/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/loglogn</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>loglogn/h</tex>-ые, <tex>(loglogn/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(loglogn/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(loglogn/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(loglogn/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < logn</tex> то мы получим не более <tex>logn</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2logn)/(cloglogn)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2logn)/(cloglogn)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2logn)/(cloglogn)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/loglogn)^g</tex> и мы работаем на уровнях не ниже чем <tex>2logloglogn</tex>, то алгоритм занимает <tex>O(qtloglogn/(g(logh - logloglogn) -logloglogn)) = O(loglogn)</tex> времени.<br />
<br />
Мы разделили <tex>q</tex> чисел <tex>p</tex> числами в каждый набор. То есть мы получили <tex>S_{0}</tex> < {<tex>e_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>e_{p}</tex>} < <tex>S_{p}</tex>, где <tex>e_{i}</tex> это сегмент <tex>a_{i}</tex> полученный с помощью битового сокращения. Мы получили такое разделение комбинированием всех поднаборов в подзадачах. Предположим числа хранятся в массиве <tex>B</tex> так, что числа в <tex>S_{i}</tex> предшествуют числам в <tex>S_{j}</tex> если <tex>i < j</tex> и <tex>e_{i}</tex> хранится после <tex>S_{i - 1}</tex> но до <tex>S_{i}</tex>. Пусть <tex>B[i]</tex> в поднаборе <tex>B[i].subset</tex>. Чтобы позволить разделению выполнится для каждого поднабора мы делаем следующее. <br />
<br />
Помещаем все <tex>B[j]</tex> в <tex>B[j].subset</tex><br />
<br />
На это потребуется линейное время и место.</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25150Сортировка Хана2012-06-12T03:16:48Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(log(m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(nloglogn)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = logn/(clogp)</tex> для константы <tex>c > 1</tex>. <tex>h/loglognlogp</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(logn)/(cloglogn)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/loglogn</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/loglogn</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2logn)/(cloglogn)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/loglogn</tex> бит в каждом контейнере. Сначала упаковываем все сегменты в <tex>(2logn)/(cloglogn)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>loglogn/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/loglogn</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>loglogn/h</tex>-ые, <tex>(loglogn/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(loglogn/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(loglogn/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(loglogn/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < logn</tex> то мы получим не более <tex>logn</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2logn)/(cloglogn)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2logn)/(cloglogn)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2logn)/(cloglogn)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.<br />
<br />
Как уже говорилось ранее после <tex>g</tex> сокращений бит мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму два для завершения разделения <tex>q</tex> чисел с помощью <tex>p</tex> чисел на наборы. Заметим, что по природе битового сокращения, начальная задача разделения для каждого набора перешла в <tex>w</tex> подзадачи разделения на <tex>w</tex> поднаборы для какого-то числа <tex>w</tex>.<br />
<br />
Теперь для каждого набора мы собираем все его поднаборы в подзадачах в один набор. Затем используя лемму два делаем разделение. Так как мы имеем неконсервативное преимущество в <tex>(h/loglogn)^g</tex> и мы работаем на уровнях не ниже чем <tex>2logloglogn</tex>, то алгоритм занимает <tex>O(qtloglogn/(g(logh - logloglogn) -logloglogn)) = O(loglogn)</tex> времени.</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25149Сортировка Хана2012-06-12T03:00:57Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(log(m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(nloglogn)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = logn/(clogp)</tex> для константы <tex>c > 1</tex>. <tex>h/loglognlogp</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(logn)/(cloglogn)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/loglogn</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/loglogn</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2logn)/(cloglogn)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/loglogn</tex> бит в каждом контейнере. Сначала упаковываем все сегменты в <tex>(2logn)/(cloglogn)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>loglogn/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/loglogn</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>loglogn/h</tex>-ые, <tex>(loglogn/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(loglogn/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(loglogn/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(loglogn/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < logn</tex> то мы получим не более <tex>logn</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее. Предполагаем, что мы упаковали хэшированные значения для каждого контейнера в <tex>(2logn)/(cloglogn)</tex> бит. У нас есть <tex>t</tex> наборов в каждом из которых <tex>q + p</tex> хэшированных контейнеров по <tex>(2logn)/(cloglogn)</tex> бит в каждом. Эти числа должны быть отсортированы в каждом наборе. Мы комбинируем все хэш контейнеры в один pool и сортируем следующим образом.<br />
<br />
Procedure linear-Time-Sort<br />
<br />
Входные данные: <tex>r > = n^{2/5}</tex> чисел <tex>d_{i}</tex>, <tex>d_{i}</tex>.value значение числа <tex>d_{i}</tex> в котором <tex>(2logn)/(cloglogn)</tex> бит, <tex>d_{i}.set</tex> набор, в котором находится <tex>d_{i}</tex>, следует отметить что всего <tex>t</tex> наборов.<br />
<br />
1) Сортировать все <tex>d_{i}</tex> по <tex>d_{i}</tex>.value используя bucket sort. Пусть все сортированные числа в A[1..r]. Этот шаг занимает линейное время так как сортируется не менее <tex>n^{2/5}</tex> чисел.<br />
<br />
2) Поместить все A[j] в A[j].set<br />
<br />
Таким образом мы заполнили все наборы за линейное время.</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25148Сортировка Хана2012-06-12T02:47:24Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(log(m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(nloglogn)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = logn/(clogp)</tex> для константы <tex>c > 1</tex>. <tex>h/loglognlogp</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(logn)/(cloglogn)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/loglogn</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/loglogn</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2logn)/(cloglogn)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/loglogn</tex> бит в каждом контейнере. Сначала упаковываем все сегменты в <tex>(2logn)/(cloglogn)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время (сортировка рассмотрена чуть позже). После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>loglogn/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/loglogn</tex> (additional multiplicative advantage).<br />
<br />
После того, как мы повторили вышеописанный процесс <tex>g</tex> раз мы получили неконсервативное преимущество в <tex>(h/loglogn)^g</tex> раз, в то время как мы потратили только <tex>O(gqt)</tex> времени, так как каждое многократное деление делятся за линейное время <tex>O(qt)</tex>.<br />
<br />
Хэш функция, которую мы используем, находится следующим образом. Мы будем хэшировать сегменты, которые <tex>loglogn/h</tex>-ые, <tex>(loglogn/h)^2</tex>-ые, ... от всего числа. Для сегментов вида <tex>(loglogn/h)^t</tex>, получаем нарезанием всех <tex>p</tex> чисел на <tex>(loglogn/h)^t</tex> сегментов. Рассматривая каждый сегмент как число мы получаем <tex>p(loglogn/h)^t</tex> чисел. Затем получаем одну хэш функцию для этих чисел. Так как <tex>t < logn</tex> то мы получим не более <tex>logn</tex> хэш функций.<br />
<br />
Рассмотрим сортировку за линейное время о которой было упомянуто ранее.</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25147Сортировка Хана2012-06-12T02:33:20Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(log(m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(nloglogn)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас. Предположим у нас есть набор <tex>T</tex> из <tex>p</tex> чисел, которые уже отсортированы как <tex>a_{1}, a_{2}, ..., a_{p}</tex>, и мы хотим использовать числа в <tex>T</tex> для разделения <tex>S</tex> из <tex>q</tex> чисел <tex>b_{1}, b_{2}, ..., b_{q}</tex> в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> что <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Назовем это разделением <tex>q</tex> чисел <tex>p</tex> числами. Пусть <tex>h = logn/(clogp)</tex> для константы <tex>c > 1</tex>. <tex>h/loglognlogp</tex> битные числа могут быть хранены в одном контейнере, так что одно слово хранит <tex>(logn)/(cloglogn)</tex> бит. Сначала рассматриваем биты в каждом <tex>a_{i}</tex> и каждом <tex>b_{i}</tex> как сегменты одинаковой длины <tex>h/loglogn</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки мы хэштруем числа в этих контейнерах (<tex>a_{i}</tex>-ом и <tex>b_{i}</tex>-ом) чтобы получить <tex>h/loglogn</tex> хэшированных значений в одном контейнере. Чтобы получить значения сразу, при вычислении хэш значений сегменты не влияют друг на друга, мы можем даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хэш значения считаются за константное время. Затем, посчитав значения мы объединяем два контейнера в один. Пусть <tex>a'_{i}</tex> хэш контейнер для <tex>a_{i}</tex>, аналогично <tex>b'_{i}</tex>. В сумме хэш значения имеют <tex>(2logn)/(cloglogn)</tex> бит. Хотя эти значения разделены на сегменты по <tex>h/loglogn</tex> бит в каждом контейнере. Сначала упаковываем все сегменты в <tex>(2logn)/(cloglogn)</tex> бит. Потом рассмотрим каждый хэш контейнер как число и отсортируем эти хэш слова за линейное время. После этой сортировки биты в <tex>a_{i}</tex> и <tex>b_{i}</tex> разрезаны на <tex>loglogn/h</tex>. Таким образом мы получили дополнительное мультипликативное преимущество в <tex>h/loglogn</tex> (additional multiplicative advantage).</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25146Сортировка Хана2012-06-12T02:08:12Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(log(m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(nloglogn)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.<br />
<br />
Спуск вниз можно рассматривать как сортировку <tex>q</tex> чисел в каждом наборе вместе с <tex>p</tex> числами <tex>a_{1}, a_{2}, ..., a_{p}</tex> из Э.П.дерева, так, что эти <tex>q</tex> чисел разделены в <tex>p + 1</tex> наборов <tex>S_{0}, S_{1}, ..., S_{p}</tex> таких, что <tex>S_{0} < </tex>{<tex>a_{1}</tex>} < ... < {<tex>a_{p}</tex>}<tex> < S_{p}</tex>.<br />
<br />
Так как нам не надо полностью сортировать <tex>q</tex> чисел и <tex>q = p^2</tex>, есть возможность использовать лемму 2 для сортировки. Для этого нам надо неконсервативное преимущество которое мы получим ниже. Для этого используем линейную технику многократного деления (multi-dividing technique) чтобы добиться этого.<br />
<br />
Для этого воспользуемся signature sorting. Адаптируем этот алгоритм для нас.</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25145Сортировка Хана2012-06-12T01:57:08Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка n целых чисел в sqrt(n) наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием O(nloglogn) времени и памяти==<br />
Для сортировки <tex>n</tex> целых чисел в диапазоне от {<tex>0, 1, ..., m - 1</tex>} мы предполагаем, что используем контейнер длины <tex>O(log(m + n))</tex> в нашем консервативном алгоритме. Мы всегда считаем что все числа упакованы в контейнеры одинаковой длины. <br />
<br />
Берем <tex>1/e = 5</tex> для экспоненциального поискового дереве Андерссона. Поэтому у корня будет <tex>n^{1/5}</tex> детей и каждое Э.П.дерево в каждом ребенке будет иметь <tex>n^{4/5}</tex> листьев. В отличии от оригинального дерева, мы будем вставлять не один элемент за раз а <tex>d^2</tex>, где <tex>d</tex> {{---}} количество детей узла дерева, где числа должны спуститься вниз.<br />
Но мы не будем сразу опускать донизу все <tex>d^2</tex> чисел. Мы будем полностью опускать все <tex>d^2</tex> чисел на один уровень. В корне мы опустим <tex>n^{2/5}</tex> чисел на следующий уровень. После того, как мы опустили все числа на следующий уровень мы успешно разделили числа на <tex>t_{1} = n^{1/5}</tex> наборов <tex>S_{1}, S_{2}, ..., S_{t_{1}}</tex>, в каждом из которых <tex>n^{4/5}</tex> чисел и <tex>S_{i} < S_{j}, i < j</tex>. Затем мы берем <tex>n^{(4/5)(2/5)}</tex> чисел из <tex>S_{i}</tex> за раз и опускаем их на следующий уровень Э.П.дерева. Повторяем это, пока все числа не опустятся на следующий уровень. На этом шаге мы разделили числа на <tex>t_{2} = n^{1/5}n^{4/25} = n^{9/25}</tex> наборов <tex>T_{1}, T_{2}, ..., T_{t_{2}}</tex> в каждом из которых <tex>n^{16/25}</tex> чисел, аналогичным наборам <tex>S_{i}</tex>. Теперь мы можем дальше опустить числа в нашем Э.П.дереве.<br />
<br />
Нетрудно заметить, что ребалансирока занимает <tex>O(nloglogn)</tex> времени с <tex>O(n)</tex> временем на уровень. Аналогично стандартному Э.П.дереву Андерссона.<br />
<br />
Нам следует нумеровать уровни Э.П.дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <tex>s</tex>. Мы имеем <tex>t = n^{1 - (4/5)^s}</tex> наборов по <tex>n^{(4/5)^s}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <tex>p = n^{(1/5)(4/5)^s}</tex> детей, то на <tex>s + 1</tex> уровень мы опустим <tex>q = n^{(2/5)(4/5)^s}</tex> чисел для каждого набора или всего <tex>qt >= n^{2/5}</tex> чисел для всех наборов за один раз.</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25144Сортировка Хана2012-06-12T01:24:40Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка <tex>n</tex> целых чисел в <tex>\sqrt{n}</tex> наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
начало<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = </tex>{<tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>}, если <tex>t > sqrt{n}</tex>, вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)<br />
<br />
конец<br />
<br />
Время работы алгоритма <tex>O(nloglogn/logk)</tex>, что доказывает лемму 2.<br />
<br />
==Собственно сортировка с использованием <tex>O(nloglogn) времени и памяти</tex>==</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25143Сортировка Хана2012-06-12T01:16:06Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка <tex>n</tex> целых чисел в <tex>\sqrt{n}</tex> наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <br />
<br />
<tex>if level == 1</tex> тогда изучить размер набора. Если размер меньше или равен <tex>\sqrt{n}</tex>, то <tex>return</tex>. Иначе разделить этот набор в <= 3 набора используя лемму три, чтобы найти медиану а затем использовать лемму 6 для сортировки. Для набора где все элементы равны медиане, не рассматривать текущий блок и текущим блоком сделать следующий. Создать маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направьте маркер для каждого числа назад к месту, где число находилось в начале. Также направьте двубитное число для каждого входного числа, указывающее на текущий блок. <tex>Return</tex>.<br />
<br />
2) <br />
<br />
От <tex>u = 1</tex> до <tex>k</tex><br />
<br />
2.1) Упаковать <tex>a^{(u)}_{i}</tex>-ый в часть из <tex>1/k</tex>-ых номеров контейнеров, где <tex>a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <tex>1/k</tex>-ых битов <tex>a_{i}</tex> и у которого текущий блок это самый крупный блок.<br />
<br />
2.2) Вызвать Sort(<tex>kloglogn</tex>, <tex>level - 1</tex>, <tex>a^{(u)}_{0}</tex>, <tex>a^{(u)}_{1}</tex>, ..., <tex>a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к которому набору это число относится, уже отправлен назад к месту где число находится во входных данных. Число имеющее наибольшее число бит в <tex>a_{i}</tex>, показывающее на ткущий блок в нем, так же отправлено назад к <tex>a_{i}</tex>.<br />
<br />
2.3) Отправить <tex>a_{i}-ые к их наборам, используя лемму шесть.</tex><br />
<br />
end.<br />
<br />
Algorithm IterateSort<br />
Call Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{n - 1}</tex>);<br />
<br />
от 1 до 5<br />
<br />
Поместить <tex>a_{i}</tex> в соответствующий набор с помощью bucket sort потому, что наборов около <tex>\sqrt{n}</tex><br />
<br />
Для каждого набора <tex>S = {a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}}</tex>, если <tex>t > sqrt{n}</tex>, то вызвать Sort(<tex>kloglogn</tex>, <tex>log_{k}((logn)/4)</tex>, <tex>a_{i_{0}}, a_{i_{1}}, ..., a_{i_{t}}</tex>)</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25142Сортировка Хана2012-06-12T00:31:52Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка <tex>n</tex> целых чисел в <tex>\sqrt{n}</tex> наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.<br />
<br />
Рассмотрим число <tex>a</tex>, которое является <tex>i</tex>-ым в наборе <tex>S</tex>. Рассмотрим блок <tex>a</tex> (назовем его <tex>a'</tex>), который является <tex>i</tex>-ым м.ч. в <tex>S</tex>. Когда мы используем вышеописанный метод перемещения нескольких следующих блоков <tex>a</tex> (назовем это <tex>a''</tex>) в <tex>S</tex>, <tex>a''</tex> просто перемещен на позицию в наборе <tex>S</tex>, но не обязательно на позицию <tex>i</tex> (где расположен <tex>a'</tex>). Если значение блока <tex>a'</tex> одинаково для всех чисел в <tex>S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <tex>S</tex> помещен <tex>a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому мы поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда мы помещаем больше бит в набор мы помещаем последующие блоки вместе с текущим блоком в набор. Так вот, в вышеописанном процессе помещения мы предполагаем, что самый значимый блок среди <tex>kloge</tex> блоков это текущий блок. Таким образом после того как мы поместили эти <tex>kloge</tex> блоков в набор мы удаляем изначальный текущий блок, потому что мы знаем, что эти <tex>kloge</tex> блоков перемещены в правильный набор и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <tex>kloge</tex> блоках.<br />
<br />
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы четыре, пять и шесть расчитанны на не очень маленькие наборы. Но поскольку мы сортируем набор из <tex>n</tex> элементов в наборы размера <tex>\sqrt{n}</tex>, то проблем не должно быть.<br />
<br />
Собственно алгоритм:<br />
<br />
Algorithm Sort(<tex>kloglogn</tex>, <tex>level</tex>, <tex>a_{0}</tex>, <tex>a_{1}</tex>, ..., <tex>a_{t}</tex>)<br />
<br />
<tex>kloglogn</tex> это неконсервативное преимущество, <tex>a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <tex>level</tex> это уровень рекурсии.<br />
<br />
1) <tex>if level == 1</tex> тогда изучить размер набора. Если</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25139Сортировка Хана2012-06-12T00:00:51Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка <tex>n</tex> целых чисел в <tex>\sqrt{n}</tex> наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.<br />
<br />
Сложная часть алгоритма заключается в том, как поместить маленькие числа в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <tex>n</tex> чисел уже поделены в <tex>e</tex> наборов. Мы можем использовать <tex>loge</tex> битов чтобы сделать марки для каждого набора. Теперь хотелось бы использовать лемму шесть. Полный размер маркера для каждого контейнера должен быть <tex>logn/2</tex>, и маркер использует <tex>loge</tex> бит, количество маркеров <tex>g</tex> в каждом контейнере должно быть не более <tex>logn/(2loge)</tex>. В дальнейшем т.к. <tex>g = logn/(2loge)</tex> м.ч. должны влезать в контейнер. Каждый контейнер содержит <tex>kloglognlogn</tex> блоков, каждое м.ч. может содержать <tex>O(klogn/g) = O(kloge)</tex> блоков. Заметим, что мы используем неконсервативное преимущество в <tex>loglogn</tex> для использования леммы шесть. Поэтому мы предполагаем что <tex>logn/(2loge)</tex> м.ч. в каждом из которых <tex>kloge</tex> блоков битов числа упакованный в один контейнер. Для каждого м.ч. мы используем маркер из <tex>loge</tex> бит, который показывает к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры как и м.ч. Так как каждый контейнер для маркеров содержит <tex>logn/(2loge)</tex> маркеров, то для каждого контейнера требуется <tex>(logn)/2</tex> бит. Таким образом лемма шесть может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <tex>O((nloge)/logn)</tex> контейнеров то время необходимое для помещения м.ч. в их наборы потребуется <tex>O((nloge)/logn)</tex> времени.<br />
<br />
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы шесть.<br />
<br />
При таком помещении мы сразу сталкиваемся со следующей проблемой.</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25131Сортировка Хана2012-06-11T23:14:28Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка <tex>n</tex> целых чисел в <tex>\sqrt{n}</tex> наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой. Пусть <tex>S'_{2}</tex> это набор чисел, у которых наибольший блок находится в <tex>S_{2}</tex>. Тогда мы можем убрать убрать <tex>logm/logn</tex> бит (наибольший блок) из каждого числа из <tex>S'_{2}</tex> из дальнейшего рассмотрения. Таким образом после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <tex>logn</tex> блоков, для каждого числа потребуется не более <tex>logn</tex> стадий чтобы поместить его в набор половинного размера. За <tex>2logn</tex> стадий все числа будут отсортированы. Так как на каждой стадии мы работаем с <tex>n/logn</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, мы затратим <tex>O(n)</tex> времени из-за <tex>2logn</tex> стадий.</div>Da1s60http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0_%D0%A5%D0%B0%D0%BD%D0%B0&diff=25130Сортировка Хана2012-06-11T22:58:23Z<p>Da1s60: </p>
<hr />
<div>'''Сортировка Хана (Yijie Han)''' {{---}} сложный алгоритм сортировки целых чисел со сложностью <tex>O(nloglog n)</tex>, где <tex>n</tex> {{---}} количество элементов для сортировки.<br />
<br />
Данная статья писалась на основе брошюры Хана, посвященной этой сортировке. Изложение материала в данной статье идет примерно в том же порядке, в каком она предоставлена в работе Хана.<br />
<br />
== Алгоритм ==<br />
Алгоритм построен на основе экспоненциального поискового дерева (далее {{---}} Э.П.дерево) Андерсона (Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в Э.П.дерево.<br />
<br />
== Andersson's exponential search tree ==<br />
Э.П.дерево с <tex>n</tex> листьями состоит из корня <tex>r</tex> и <tex>n^e</tex> (0<<tex>e</tex><1) Э.П.поддеревьев, в каждом из которых <tex>n^{1 - e}</tex> листьев; каждое Э.П.поддерево является сыном корня <tex>r</tex>. В этом дереве <tex>O(n \log\log n)</tex> уровней. При нарушении баланса дерева, необходимо балансирование, которое требует <tex>O(n \log\log n)</tex> времени при <tex>n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не по одиночке, как изначально предлагает Андерссон.<br />
<br />
==Необходимая информация==<br />
{{Определение<br />
|id=def1. <br />
|definition=<br />
Контейнер {{---}} объект определенного типа, содержащий обрабатываемый элемент. Например __int32, __int64, и т.д.<br />
}}<br />
{{Определение<br />
|id=def2. <br />
|definition=<br />
Алгоритм сортирующий <tex>n</tex> целых чисел из множества <tex>\{0, 1, \ldots, m - 1\}</tex> называется консервативным, если длина контейнера (число бит в контейнере), является <tex>O(\log(m + n))</tex>. Если длина больше, то алгоритм неконсервативный.<br />
}}<br />
{{Определение<br />
|id=def3. <br />
|definition=<br />
Если мы сортируем целые числа из множества {0, 1, ..., <tex>m</tex> - 1} с длиной контейнера <tex>klog(m + n)</tex> с <tex>k</tex> >= 1, тогда мы сортируем с неконсервативным преимуществом <tex>k</tex>.<br />
}}<br />
{{Определение<br />
|id=def4. <br />
|definition=<br />
Для множества <tex>S</tex> определим<br />
min(<tex>S</tex>) = min(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
max(<tex>S</tex>) = max(<tex>a</tex>:<tex>a</tex> принадлежит <tex>S</tex>)<br />
Набор <tex>S1</tex> < <tex>S2</tex> если max(<tex>S1</tex>) <= min(<tex>S2</tex>)<br />
}}<br />
<br />
==Уменьшение числа бит в числах==<br />
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <tex>O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <tex>O(n)</tex>. Для того, чтобы еще ускорить алгоритм нам необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хэширование для всех чисел хранимых в контейнере. Для этого используется хэш функция для хэширования <tex>n</tex> чисел в таблицу размера <tex>O(n^2)</tex> за константное время, без коллизий. Для этого используется хэш модифицированная функция авторства: Dierzfelbinger и Raman.<br />
<br />
Алгоритм: Пусть целое число <tex>b >= 0</tex> и пусть <tex>U = \{0, \ldots, 2^b - 1\}</tex>. Класс <tex>H_{b,s}</tex> хэш функций из <tex>U</tex> в <tex>\{0, \ldots, 2^s - 1\}</tex> определен как <tex>H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\mod 2)\}</tex> и для всех <tex>x</tex> из <tex>U: h_{a}(x) = (ax \mod 2^b) div 2^{b - s}</tex>.<br />
<br />
Данный алгоритм базируется на следующей лемме:<br />
<br />
Номер один.<br />
{{Лемма<br />
|id=lemma1. <br />
|statement=<br />
Даны целые числа <tex>b</tex> >= <tex>s</tex> >= 0 и <tex>T</tex> является подмножеством {0, ..., <tex>2^b</tex> - 1}, содержащим <tex>n</tex> элементов, и <tex>t</tex> >= <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <tex>h_{a}</tex> принадлежащая <tex>H_{b,s}</tex> может быть выбрана за время <tex>O(bn^2)</tex> так, что количество коллизий <tex>coll(h_{a}, T) <= t</tex><br />
}}<br />
<br />
Взяв <tex>s = 2logn</tex> мы получим хэш функцию <tex>h_{a}</tex> которая захэширует <tex>n</tex> чисел из <tex>U</tex> в таблицу размера <tex>O(n^2)</tex> без коллизий. Очевидно, что <tex>h_{a}(x)</tex> может быть посчитана для любого <tex>x</tex> за константное время. Если мы упакуем несколько чисел в один контейнер так, что они разделены несколькими битами нулей, мы спокойно сможем применить <tex>h_{a}</tex> ко всему контейнеру, а в результате все хэш значения для всех чисел в контейере были посчитаны. Заметим, что это возможно только потому, что в вычисление хэш знчения вовлечены только (mod <tex>2^b</tex>) и (div <tex>2^{b - s}</tex>). <br />
<br />
Такая хэш функция может быть найдена за <tex>O(n^3)</tex>.<br />
<br />
Следует отметить, что несмотря на размер таблицы <tex>O(n^2)</tex>, потребность в памяти не превышает <tex>O(n)</tex> потому, что хэширование используется только для уменьшения количества бит в числе.<br />
<br />
==Signature sorting==<br />
В данной сортировке используется следующий алгоритм:<br />
<br />
Предположим, что <tex>n</tex> чисел должны быть сортированы, и в каждом <tex>logm</tex> бит. Мы рассматриваем, что в каждом числе есть <tex>h</tex> сегментов, в каждом из которых <tex>log(m/h)</tex> бит. Теперь мы применяем хэширование ко всем сегментам и получаем <tex>2hlogn</tex> бит хэшированных значений для каждого числа. После сортировки на хэшированных значениях для всех начальных чисел начальная задача по сортировке <tex>n</tex> чисел по <tex>m</tex> бит в каждом стала задачей по сортировке <tex>n</tex> чисел по <tex>log(m/h)</tex> бит в каждом.<br />
<br />
Так же, рассмотрим проблему последующего разделения. Пусть <tex>a_{1}</tex>, <tex>a_{2}</tex>, ..., <tex>a_{p}</tex> {{---}} <tex>p</tex> чисел и <tex>S</tex> {{---}} множество чисeл. Мы хотим разделить <tex>S</tex> в <tex>p + 1</tex> наборов таких, что: <tex>S_{0}</tex> < {<tex>a_{1}</tex>} < <tex>S_{1}</tex> < {<tex>a_{2}</tex>} < ... < {<tex>a_{p}</tex>} < <tex>S_{p}</tex>. Т.к. мы используем signature sorting, до того как делать вышеописанное разделение, мы поделим биты в <tex>a_{i}</tex> на <tex>h</tex> сегментов и возьмем некоторые из них. Мы так же поделим биты для каждого числа из <tex>S</tex> и оставим только один в каждом числе. По существу для каждого <tex>a_{i}</tex> мы возьмем все <tex>h</tex> сегментов. Если соответствующие сегменты <tex>a_{i}</tex> и <tex>a_{j}</tex> совпадают, то нам понадобится только один. Сегменты, которые мы берем для числа в <tex>S</tex>, {{---}} сегмент, который выделяется из <tex>a_{i}</tex>. Таким образом мы преобразуем начальную задачу о разделении <tex>n</tex> чисел в <tex>logm</tex> бит в несколько задач на разделение с числами в <tex>log(m/h)</tex> бит.<br />
<br />
Пример:<br />
<br />
<tex>a_{1}</tex> = 3, <tex>a_{2}</tex> = 5, <tex>a_{3}</tex> = 7, <tex>a_{4}</tex> = 10, S = {1, 4, 6, 8, 9, 13, 14}.<br />
<br />
Мы разделим числа на 2 сегмента. Для <tex>a_{1}</tex> получим верхний сегмент 0, нижний 3; <tex>a_{2}</tex> верхний 1, нижний 1; <tex>a_{3}</tex> верхний 1, нижний 3; <tex>a_{4}</tex> верхний 2, нижний 2. Для элементов из S получим: для 1: нижний 1 т.к. он выделяется из нижнего сегмента <tex>a_{1}</tex>; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.<br />
<br />
==Сортировка на маленьких целых==<br />
Для лучшего понимания действия алгоритма и материала, изложенного в данной статье, в целом, ниже представлены несколько полезных лемм. <br />
<br />
Номер два.<br />
{{Лемма<br />
|id=lemma2. <br />
|statement=<br />
<tex>n</tex> целых чисел можно отсортировать в <tex>\sqrt{n}</tex> наборов <tex>S_{1}</tex>, <tex>S_{2}</tex>, ..., <tex>S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <tex>\sqrt{n}</tex> чисел и <tex>S_{i}</tex> < <tex>S_{j}</tex> при <tex>i</tex> < <tex>j</tex>, за время <tex>O(nloglogn/logk)</tex> и место <tex>O(n)</tex> с не консервативным преимуществом <tex>kloglogn</tex><br />
|proof=<br />
Доказательство данной леммы будет приведено далее в тексте статьи.<br />
}}<br />
<br />
Номер три.<br />
{{Лемма<br />
|id=lemma3. <br />
|statement=<br />
Выбор <tex>s</tex>-ого наибольшего числа среди <tex>n</tex> чисел упакованных в <tex>n/g</tex> контейнеров может быть сделана за <tex>O(nlogg/g)</tex> время и с использованием <tex>O(n/g)</tex> места. Конкретно медиана может быть так найдена.<br />
|proof=<br />
Так как мы можем делать попарное сравнение <tex>g</tex> чисел в одном контейнере с <tex>g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, мы можем упаковать медианы из первого, второго, ..., <tex>g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом набор <tex>S</tex> из медиан теперь содержится в <tex>n/(5g)</tex> контейнерах. Рекурсивно находим медиану <tex>m</tex> в <tex>S</tex>. Используя <tex>m</tex> уберем хотя бы <tex>n/4</tex> чисел среди <tex>n</tex>. Затем упакуем оставшиеся из <tex>n/g</tex> контейнеров в <tex>3n/4g</tex> контейнеров и затем продолжим рекурсию.<br />
}}<br />
<br />
Номер четыре.<br />
{{Лемма<br />
|id=lemma4.<br />
|statement=<br />
Если <tex>g</tex> целых чисел, в сумме использующие <tex>(logn)/2</tex> бит, упакованы в один контейнер, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы за время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
|proof=<br />
Так как используется только <tex>(logn)/2</tex> бит в каждом контейнере для хранения <tex>g</tex> чисел, мы можем использовать bucket sorting чтобы отсортировать все контейнеры. представляя каждый как число, что занимает <tex>O(n/g)</tex> времени и места. Потому, что мы используем <tex>(logn)/2</tex> бит на контейнер нам понадобится <tex>\sqrt {n}</tex> шаблонов для всех контейнеров. Затем поместим <tex>g < (logn)/2</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <tex>g - 1</tex> контейнеров которые не смогут образовать группу. Поэтому не более <tex>\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы мы помещаем <tex>i</tex>-е число во всех <tex>g</tex> контейнерах в один. Таким образом мы берем <tex>g</tex> <tex>g</tex>-целых векторов и получаем <tex>g</tex> <tex>g</tex>-целых векторов где <tex>i</tex>-ый вектор содержит <tex>i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <tex>O(glogg)</tex>, с использованием <tex>O(g)</tex> места. Для всех групп это занимает время <tex>O((n/g)logg)</tex>, с использованием <tex>O(n/g)</tex> места.<br />
<br />
Для контейнеров вне групп (которых <tex>\sqrt(n)(g - 1)</tex> штук) мы просто разберем и соберем заново контейнеры. На это потребуется не более <tex>O(n/g)</tex> места и времени. После всего этого мы используем bucket sorting вновь для сортировки <tex>n</tex> контейнеров. таким образом мы отсортируем все числа.<br />
}}<br />
<br />
Заметим, что когда <tex>g = O(logn)</tex> мы сортируем <tex>O(n)</tex> чисел в <tex>n/g</tex> контейнеров за время <tex>O((n/g)loglogn)</tex>, с использованием O(n/g) места. Выгода очевидна.<br />
<br />
Лемма пять.<br />
{{Лемма<br />
|id=lemma5.<br />
|statement=<br />
Если принять, что каждый контейнер содержит <tex>logm > logn</tex> бит, и <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и <tex>g</tex> маркеров упакованы в один контейнер таким же образом<tex>^*</tex>, что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по их маркерам за время <tex>O((nloglogn)/g)</tex> с использованием <tex>O(n/g)</tex> места.<br />
(*): если число <tex>a</tex> упаковано как <tex>s</tex>-ое число в <tex>t</tex>-ом контейнере для чисел, тогда маркер для <tex>a</tex> упакован как <tex>s</tex>-ый маркер в <tex>t</tex>-ом контейнере для маркеров.<br />
|proof=<br />
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <tex>(logn)/2</tex> бит. Сортировка сгруппирует контейнеры для чисел как в четвертой лемме. Мы можем переместить каждую группу контейнеров для чисел.<br />
}}<br />
Заметим, что сортирующие алгоритмы в четвертой и пятой леммах нестабильные. Хотя на их основе можно построить стабильные алгоритмы используя известный метод добавления адресных битов к каждому входящему числу.<br />
<br />
Если у нас длина контейнеров больше, сортировка может быть ускорена, как показано в следующей лемме.<br />
<br />
Лемма шесть.<br />
{{Лемма<br />
|id=lemma6.<br />
|statement=<br />
предположим, что каждый контейнер содержит <tex>logmloglogn > logn</tex> бит, что <tex>g</tex> чисел, в каждом из которых <tex>(logm)/g</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <tex>(logn)/(2g)</tex> бит, и что <tex>g</tex> маркеров упакованы в один контейнер тем же образом что и числа, тогда <tex>n</tex> чисел в <tex>n/g</tex> контейнерах могут быть отсортированы по своим маркерам за время <tex>O(n/g)</tex>, с использованием <tex>O(n/g)</tex> памяти.<br />
|proof=<br />
Заметим, что несмотря на то, что длина контейнера <tex>logmloglogn</tex> бит всего <tex>logm</tex> бит используется для хранения упакованных чисел. Так же как в леммах четыре и пять мы сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел мы помещаем <tex>gloglogn</tex> вместо <tex>g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе содержащей <tex>gloglogn</tex> контейнеров мы сначала упаковываем <tex>gloglogn</tex> контейнеров в <tex>g</tex> контейнеров упаковывая <tex>loglogn</tex> контейнеров в один. Далее мы делаем транспозицию над <tex>g</tex> контейнерами. Таким образом перемещение занимает всего <tex>O(gloglogn)</tex> времени для каждой группы и <tex>O(n/g)</tex> времени для всех чисел. После завершения транспозиции, мы далее распаковываем <tex>g</tex> контейнеров в <tex>gloglogn</tex> контейнеров.<br />
}}<br />
<br />
Заметим, что если длина контейнера <tex>logmloglogn</tex> и только <tex>logm</tex> бит используется для упаковки <tex>g <= logn</tex> чисел в один контейнер, тогда выбор в лемме три может быть сделан за время и место <tex>O(n/g)</tex>, потому, что упаковка в доказатльстве леммы три теперь может быть сделана за время <tex>O(n/g)</tex>.<br />
<br />
==Сортировка <tex>n</tex> целых чисел в <tex>\sqrt{n}</tex> наборов==<br />
Постановка задачи и решение некоторых проблем:<br />
<br />
Рассмотрим проблему сортировки <tex>n</tex> целых чисел из множества {0, 1, ..., <tex>m</tex> - 1} в <tex>\sqrt{n}</tex> наборов как во второй лемме. Мы предполагаем, что в каждом контейнере <tex>kloglognlogm</tex> бит и хранит число в <tex>logm</tex> бит. Поэтому неконсервативное преимущество <tex>kloglogn</tex>. Мы так же предполагаем, что <tex>logm >= lognloglogn</tex>. Иначе мы можем использовать radix sort для сортировки за время <tex>O(nloglogn)</tex> и линейную память. Мы делим <tex>logm</tex> бит, используемых для представления каждого числа, в <tex>logn</tex> блоков. Таким образом каждый блок содержит как минимум <tex>loglogn</tex> бит. <tex>i</tex>-ый блок содержит с <tex>ilogm/logn</tex>-ого по <tex>((i + 1)logm/logn - 1)</tex>-ый биты. Биты считаются с наименьшего бита начиная с нуля. Теперь у нас имеется <tex>2logn</tex>-уровневый алгоритм, который работает следующим образом:<br />
<br />
На каждой стадии мы работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.) потому, что каждое м.ч. теперь содержит только <tex>logm/logn</tex> бит. Каждое число представлено и соотносится с м.ч., над которым мы работаем в данный момент. Положим, что нулевая стадия работает с самыми большим блоком (блок номер <tex>logn - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <tex>n/logn</tex> контейнеров с <tex>logn</tex> м.ч. упакованных в один контейнер. Мы пренебрегаем временем, потраченным на на эту упаковку, считая что она бесплатна. По третьей лемме мы можем найти медиану этих <tex>n</tex> м.ч. за время и память <tex>O(n/logn)</tex>. Пусть <tex>a</tex> это найденная медиана. Тогда <tex>n</tex> м.ч. могут быть разделены на не более чем три группы: <tex>S_{1}</tex>, <tex>S_{2}</tex> и <tex>S_{3}</tex>. <tex>S_{1}</tex> содержит м.ч. которые меньше <tex>a</tex>, <tex>S_{2}</tex> содержит м.ч. равные <tex>a</tex>, <tex>S_{3}</tex> содержит м.ч. большие <tex>a</tex>. Так же мощность <tex>S_{1}</tex> и <tex>S_{3} </tex><= <tex>n/2</tex>. Мощность <tex>S_{2}</tex> может быть любой.</div>Da1s60