Изменения
Нет описания правки
'''Сортировка Хана ''' (Yijie Han)англ. ''Hansort'' ) {{---}} сложный алгоритм сортировки целых чисел со сложностью <texdpi="130">O(n \log\log n)</tex>, где <texdpi="130">n</tex> {{---}} количество элементов для сортировки.
Данная статья писалась на основе брошюры Хана(англ. ''Yijie Han''), посвященной этой сортировке.
== Описание ==
Алгоритм построен на основе '''экспоненциального поискового дерева (далее {{---}} ЭП-дерево) Андерсона ''' (англ. ''Andersson's exponential search tree''). Сортировка происходит за счет вставки целых чисел в экспоненциальное поисковое дерево (''далее {{---}} ЭП-дерево'').
== Andersson's exponential search tree Экспоненциальное поисковое дерево Андерсона ==
{{Определение
|definition = '''ЭП-дерево ''' {{- --}} это дерево поиска, в котором все ключи хранятся в листьях этого дерева и количество детей у каждого узла уменьшается экспоненциально от глубины узла.
}}
Структура ЭП-дерева:
1) Корень имеет <texdpi="130">\Theta (n^e)</tex> сыновей (<texdpi="130"> ( 0 < e < 1 )</tex>). Все сыновья являются ЭП-деревьями.
2) Каждое поддерево корня имеет <texdpi="130">\Theta(n^{1-e})</tex> сыновей.
В этом дереве <texdpi="130">O(n \log\log n)</tex> уровней. При нарушении баланса дерева необходимо балансирование, которое требует <texdpi="130">O(n \log\log n)</tex> времени при <texdpi="130">n</tex> вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не поодиночке, как изначально предлагал Андерссон.
==Определения==
'''Контейнер''' {{---}} объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.}}
{{ Определение | definition =
Алгоритм, сортирующий <texdpi="130">n</tex> целых чисел из множества <texdpi="130">\{0, 1, \ldots, m - 1\}</tex>, называется '''консервативным''', если длина контейнера (число бит в контейнере) равна <texdpi="130">O(\log(m + n))</tex>. Если длина больше, то алгоритм '''неконсервативный'''.
}}
{{ Определение | definition =
Если сортируются целые числа из множества <texdpi="130">\{0, 1, \ldots, m - 1\}</tex> с длиной контейнера <texdpi="130">k \log (m + n)</tex> с <texdpi="130">k \geqslant 1</tex>, тогда сортировка происходит с '''неконсервативным преимуществом''' <texdpi="130">k</tex>.
}}
{{ Определение | definition =
Для множества <texdpi="130">S</tex> определим
<texdpi="130">\min(S) = \min\limits_{a \in S} a</tex>
<texdpi="130">\max(S) = \max\limits_{a \in S} a</tex>
Набор <texdpi="130">S1 < S2</tex> если <texdpi="130">\max(S1) \leqslant \min(S2)</tex>
}}
{{ Определение | definition =
Предположим, есть набор <texdpi="130">T</tex> из <texdpi="130">p</tex> чисел, которые уже отсортированы как <texdpi="130">a_{1}, a_{2}, \ldots, a_{p}</tex> и набор <texdpi="130">S</tex> из <texdpi="130">q</tex> чисел <texdpi="130">b_{1}, b_{2}, \ldots, b_{q}</tex>. Тогда '''разделением''' <texdpi="130">q</tex> чисел <texdpi="130">p</tex> числами называется <texdpi="130">p + 1</tex> набор <texdpi="130">S_{0}, S_{1}, \ldots, S_{p}</tex>, где <texdpi="130">S_{0} < a_{1} < S_{1} < \ldots < a_{p} < S_{p}</tex>.
}}
|about = № 1
|statement =
Даны целые числа <texdpi="130">b</tex> <tex>\geqslant</tex> <tex>s</tex> <tex>\geqslant0</tex> 0, и <texdpi="130">T</tex> является подмножеством множества <texdpi="130">\{0, \ldots, 2^b - 1\}</tex>, содержащим <texdpi="130">n</tex> элементов, и <texdpi="130">t</tex> <tex>\geqslant</tex> <tex>2^{-s + 1}</tex>С<tex>^k_{n}</tex>. Функция <texdpi="130">h_{a}</tex>, принадлежащая <texdpi="130">H_{b,s}</tex>, может быть выбрана за время <texdpi="130">O(bn^2)</tex> так, что количество коллизий <texdpi="130">coll(h_{a}, T)</tex> <tex>\leqslant</tex> <tex>t</tex>.
}}
|about = № 2
|statement =
Выбор <texdpi="130">s</tex>-ого наибольшего числа среди <texdpi="130">n</tex> чисел, упакованных в <texdpi="150">\frac{n}{g}</tex> контейнеров, может быть сделан за время <texdpi="150">O(\frac{n \log g}{g})</tex> и с использованием <texdpi="150">O(\frac{n}{g})</tex> памяти. В том числе, так может быть найдена медиана.
|proof =
Так как возможно делать попарное сравнение <texdpi="130">g</tex> чисел в одном контейнере с <texdpi="130">g</tex> числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, <texdpi="130">\ldots</tex>, <texdpi="130">g</tex>-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом, набор <texdpi="130">S</tex> из медиан теперь содержится в <texdpi="150">\frac{n}{5g}</tex> контейнерах. Рекурсивно находим медиану <texdpi="130">m</tex> в <texdpi="130">S</tex>. Используя <texdpi="130">m</tex>, уберем хотя бы <texdpi="150">\frac{n}{4}</tex> чисел среди <texdpi="130">n</tex>. Затем упакуем оставшиеся из <texdpi="150">\frac{n}{g}</tex> контейнеров в <texdpi="150">\frac{3n}{4g}</tex> контейнеров и затем продолжим рекурсию.
}}
|about = № 3
|statement =
Если <texdpi="130">g</tex> целых чисел, в сумме использующих <texdpi="150">\frac{\log n}{2}</tex> бит, упакованы в один контейнер, тогда <texdpi="130">n</tex> чисел в <texdpi="150">\frac{n}{g}</tex> контейнерах могут быть отсортированы за время <texdpi="150">O(\frac{n \log g}{g})</tex> с использованием <texdpi="150">O(\frac{n}{g})</tex> памяти.
|proof =
Так как используется только <texdpi="150">\frac{\log n}{2}</tex> бит в каждом контейнере для хранения <texdpi="130">g</tex> чисел, используем bucket sort, чтобы отсортировать все контейнеры, представляя каждый как число, что занимает <texdpi="150">O(\frac{n}{g})</tex> времени и памяти. Так как используется <texdpi="150">\frac{\log n}{2}</tex> бит на контейнер, понадобится <texdpi="130">\sqrt{n}</tex> шаблонов для всех контейнеров. Затем поместим <texdpi="150">g < \frac{\log n}{2}</tex> контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более <texdpi="130">g - 1</tex> контейнеров, которые не смогут образовать группу. Поэтому не более <texdpi="130">\sqrt{n}(g - 1)</tex> контейнеров не смогут сформировать группу. Для каждой группы помещаем <texdpi="130">i</tex>-е число во всех <texdpi="130">g</tex> контейнерах в один. Таким образом берутся <texdpi="130">g</tex> <texdpi="130">g</tex>-целых векторов и получаются <texdpi="130">g</tex> <texdpi="130">g</tex>-целых векторов, где <texdpi="130">i</tex>-ый вектор содержит <texdpi="130">i</tex>-ое число из входящего вектора. Эта транспозиция может быть сделана за время <texdpi="130">O(g \log g)</tex>, с использованием <texdpi="130">O(g)</tex> памяти. Для всех групп это занимает время <texdpi="150">O(\frac{n \log g}{g})</tex>, с использованием <texdpi="150">O(\frac{n}{g})</tex> памяти.
Для контейнеров вне групп (которых <texdpi="130">\sqrt{n}(g - 1)</tex> штук) разбираем и собираем заново контейнеры. На это потребуется не более <texdpi="150">O(\frac{n}{g})</tex> времени и памяти. После всего этого используем карманную сортировку вновь для сортировки <texdpi="130">n</tex> контейнеров. Таким образом, все числа отсортированы.
Заметим, что когда <texdpi="130">g = O( \log n)</tex>, сортировка <texdpi="130">O(n)</tex> чисел в <texdpi="150">\frac{n}{g}</tex> контейнеров произойдет за время <texdpi="150">O((\frac{n}{g}) </tex> <tex dpi="130">\log\log n)</tex> с использованием <texdpi="150">O(\frac{n}{g})</tex> памяти. Выгода очевидна.
}}
|about = № 4
|statement =
Примем, что каждый контейнер содержит <texdpi="130"> \log m > \log n</tex> бит, и <texdpi="130">g</tex> чисел, в каждом из которых <texdpi="150">\frac{\log m}{g}</tex> бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий <texdpi="150">\frac{\log n}{2g}</tex> бит, и <texdpi="130">g</tex> маркеров упакованы в один контейнер таким же образом<texdpi="130">^*</tex>, что и числа, тогда <texdpi="130">n</tex> чисел в <texdpi="150">\frac{n}{g}</tex> контейнерах могут быть отсортированы по их маркерам за время <texdpi="150">O(\frac{n \log\log n}{g})</tex> с использованием <texdpi="150">O(\frac{n}{g})</tex> памяти.(*): если число <texdpi="130">a</tex> упаковано как <texdpi="130">s</tex>-ое число в <texdpi="130">t</tex>-ом контейнере для чисел, тогда маркер для <texdpi="130">a</tex> упакован как <texdpi="130">s</tex>-ый маркер в <texdpi="130">t</tex>-ом контейнере для маркеров.
|proof =
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует <texdpi="150">\frac{\log n}{2}</tex> бит. Сортировка сгруппирует контейнеры для чисел как в [[#lemma3|лемме №3]]. Перемещаем каждую группу контейнеров для чисел.
}}
|about = № 5
|statement =
Предположим, что каждый контейнер содержит <texdpi="130">\log m \log\log n > \log n</tex> бит, что <texdpi="130">g</tex> чисел, в каждом из которых <texdpi="150">\frac{\log m}{g}</tex> бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий <texdpi="150">\frac{\log n}{2g}</tex> бит, и что <texdpi="130">g</tex> маркеров упакованы в один контейнер тем же образом что и числа. Тогда <texdpi="130">n</tex> чисел в <texdpi="150">\frac{n}{g}</tex> контейнерах могут быть отсортированы по своим маркерам за время <texdpi="150">O(\frac{n}{g})</tex> с использованием <texdpi="150">O(\frac{n}{g})</tex> памяти.
|proof =
Заметим, что несмотря на то, что длина контейнера <texdpi="130">\log m \log\log n</tex> бит, всего <texdpi="130">\log m</tex> бит используется для хранения упакованных чисел. Так же как в [[#lemma3|лемме №3]] и [[#lemma4|лемме №4]] сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел, помещаем <texdpi="130">g \log\log n</tex> вместо <texdpi="130">g</tex> контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей <texdpi="130">g \log\log n</tex> контейнеров, упаковываем <texdpi="130">g \log\log n</tex> контейнеров в <texdpi="130">g</tex>, упаковывая <texdpi="130">\log\log n</tex> контейнеров в один. Далее делаем транспозицию над <texdpi="130">g</tex> контейнерами. Таким образом перемещение занимает всего <texdpi="130">O(g \log\log n)</tex> времени для каждой группы и <texdpi="150">O(\frac{n}{g})</tex> времени для всех чисел. После завершения транспозиции, распаковываем <texdpi="130">g</tex> контейнеров в <texdpi="130">g \log\log n</tex> контейнеров.
Заметим, что если длина контейнера <texdpi="130">\log m \log\log n</tex> и только <texdpi="130">\log m</tex> бит используется для упаковки <texdpi="130">g \leqslant \log n</tex> чисел в один контейнер, тогда выбор в [[#lemma2|лемме №2]] может быть сделан за время и память <texdpi="150">O(\frac{n}{g})</tex>, потому что упаковка в доказательстве [[#lemma2|лемме №2]] теперь может быть сделана за время <texdpi="150">O(\frac{n}{g})</tex>.
}}
|about = № 6
|statement =
<texdpi="130">n</tex> целых чисел можно отсортировать в <texdpi="130">\sqrt{n}</tex> наборов <texdpi="130">S_{1}</tex>, <texdpi="130">S_{2}</tex>, <texdpi="130">\ldots</tex>, <texdpi="130">S_{\sqrt{n}}</tex> таким образом, что в каждом наборе <texdpi="130">\sqrt{n}</tex> чисел и <texdpi="130">S_{i} < S_{j}</tex> при <texdpi="130">i < j</tex>, за время <texdpi="150">O(\frac{n \log\log n} {\log k})</tex> и место <texdpi="130">O(n)</tex> с неконсервативным преимуществом <texdpi="130">k \log\log n</tex>.
|proof =
Алгоритм сортировки <texdpi="130">n</tex> целых чисел в <texdpi="130">\sqrt{n}</tex> наборов, представленный ниже, является доказательством данной леммы.
Постановка задачи и решение некоторых проблем:
Рассмотрим проблему сортировки <texdpi="130">n</tex> целых чисел из множества <texdpi="130">\{0, 1, \ldots, m - 1\}</tex> в <texdpi="130">\sqrt{n}</tex> наборов, как в условии леммы. Предполагаем, что каждый контейнер содержит <texdpi="130">k \log\log n \log m</tex> бит и хранит число в <texdpi="130">\log m</tex> бит. Поэтому неконсервативное преимущество {{---}} <texdpi="130">k \log \log n</tex>. Также предполагаем, что <texdpi="130">\log m \geqslant \log n \log\log n</tex>. Иначе можно использовать radix sort для сортировки за время <texdpi="130">O(n \log\log n)</tex> и линейную память. Делим <texdpi="130">\log m</tex> бит, используемых для представления каждого числа, в <texdpi="130">\log n</tex> блоков. Таким образом, каждый блок содержит как минимум <texdpi="130">\log\log n</tex> бит. <texdpi="130">i</tex>-ый блок содержит с <texdpi="150">\frac{i \log m} {\log n}</tex>-ого по <texdpi="150">(\frac{(i + 1) \log m} {\log n - 1})</tex>-ый биты. Биты считаются с наименьшего бита, начиная с нуля. Теперь у нас имеется <texdpi="130">2 \log n</tex>-уровневый алгоритм, который работает следующим образом:
На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.), потому что каждое м.ч. теперь содержит только <texdpi="150">\frac{\log m}{\log n}</tex> бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самым большим блоком (блок номер <texdpi="130">\log n - 1</tex>). Предполагаем, что биты этих м.ч. упакованы в <texdpi="150">\frac{n}{\log n}</tex> контейнеров с <texdpi="130">\log n</tex> м.ч. упакованными в один контейнер. Пренебрегая временем, потраченным на эту упаковку, считаем, что она бесплатна. По [[#lemma2|лемме №2]] находим медиану этих <texdpi="130">n</tex> м.ч. за время и память <texdpi="150">O(\frac{n}{\log n})</tex>. Пусть <texdpi="130">a</tex> {{---}} это найденная медиана. Тогда <texdpi="130">n</tex> м.ч. могут быть разделены на не более чем три группы: <texdpi="130">S_{1}</tex>, <texdpi="130">S_{2}</tex> и <texdpi="130">S_{3}</tex>. <texdpi="130">S_{1}</tex> содержит м.ч., которые меньше <texdpi="130">a</tex>, <texdpi="130">S_{2}</tex> содержит м.ч., равные <texdpi="130">a</tex>, <texdpi="130">S_{3}</tex> содержит м.ч., большие <texdpi="130">a</tex>. Также мощность <texdpi="130">S_{1}</tex> и <texdpi="130">S_{3} </tex> не превосходит <texdpi="130">n/2</tex>. Мощность <texdpi="130">S_{2}</tex> может быть любой. Пусть <texdpi="130">S'_{2}</tex> {{---}} это набор чисел, у которых наибольший блок находится в <texdpi="130">S_{2}</tex>. Тогда убираем из дальнейшего рассмотрения <texdpi="150">\frac{\log m}{\log n}</tex> бит (наибольший блок) из каждого числа, принадлежащего <texdpi="130">S'_{2}</tex>. Таким образом, после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только <texdpi="130">\log n</tex> блоков, для каждого числа потребуется не более <texdpi="130">\log n</tex> стадий, чтобы поместить его в набор половинного размера. За <texdpi="130">2 \log n</tex> стадий все числа будут отсортированы. Так как на каждой стадии работаем с <texdpi="150">\frac{n}{\log n}</tex> контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается <texdpi="130">O(n)</tex> времени из-за <texdpi="130">2 \log n</tex> стадий.
Сложная часть алгоритма заключается в том, как поместить м.ч. в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что <texdpi="130">n</tex> чисел уже поделены в <texdpi="130">e</tex> наборов. Используем <texdpi="130">\log e</tex> битов чтобы сделать марки для каждого набора. Теперь используем [[#lemma5|лемме №5]]. Полный размер маркера для каждого контейнера должен быть <texdpi="150">\frac{\log n}{2}</tex>, и маркер использует <texdpi="130">\log e</tex> бит, значит количество маркеров <texdpi="130">g</tex> в каждом контейнере должно быть не более <texdpi="150">\frac{\log n}{2\log e}</tex>. В дальнейшем, так как <texdpi="150">g = \frac{\log n}{2 \log e}</tex>, м.ч. должны влезать в контейнер. Каждый контейнер содержит <texdpi="130">k \log\log n \log n</tex> блоков, каждое м.ч. может содержать <texdpi="150">O(\frac{k \log n}{g}) = O(k \log e)</tex> блоков. Заметим, что используется неконсервативное преимущество в <texdpi="130">\log\log n</tex> для [[#lemma5|лемме №5]] Поэтому предполагается, что <texdpi="150">\frac{\log n}{2 \log e}</tex> м.ч., в каждом из которых <texdpi="130">k \log e</tex> блоков битов числа, упакованны в один контейнер. Для каждого м.ч. используется маркер из <texdpi="130">\log e</tex> бит, который показывает, к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры, как и м.ч. Так как каждый контейнер для маркеров содержит <texdpi="150">\frac{\log n}{2 \log e}</tex> маркеров, то для каждого контейнера требуется <texdpi="150">\frac{\log n}{2}</tex> бит. Таким образом, [[#lemma5|лемма №5]] может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется <texdpi="150">O(\frac{n \log e}{ \log n})</tex> контейнеров, то время, необходимое для помещения м.ч. в их наборы, равно <texdpi="150">O(\frac{n \log e}{ \log n})</tex>.
Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из [[#lemma5|леммы №5]].
При таком помещении сразу возникает следующая проблема.
Рассмотрим число <texdpi="130">a</tex>, которое является <texdpi="130">i</tex>-ым в наборе <texdpi="130">S</tex>. Рассмотрим блок <texdpi="130">a</tex> (назовем его <texdpi="130">a'</tex>), который является <texdpi="130">i</tex>-ым м.ч. в <texdpi="130">S</tex>. Когда используется вышеописанный метод перемещения нескольких следующих блоков <texdpi="130">a</tex> (назовем это <texdpi="130">a''</tex>) в <texdpi="130">S</tex>, <texdpi="130">a''</tex> просто перемещен на позицию в наборе <texdpi="130">S</tex>, но не обязательно на позицию <texdpi="130">i</tex> (где расположен <texdpi="130">a'</tex>). Если значение блока <texdpi="130">a'</tex> одинаково для всех чисел в <texdpi="130">S</tex>, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в <texdpi="130">S</tex> помещен <texdpi="130">a''</tex>. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, последующие блоки помещаются в набор вместе с текущим блоком. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди <texdpi="130">k \log e</tex> блоков {{---}} это текущий блок. Таким образом, после того, как эти <texdpi="130">k \log e</tex> блоков помещены в набор, изначальный текущий блок удаляется, потому что известно, что эти <texdpi="130">k \log e</tex> блоков перемещены в правильный набор, и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных <texdpi="130">k \log e</tex> блоках.
Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы [[#lemma3|3]], [[#lemma4|4]], [[#lemma5|5]] расчитаны на не очень маленькие наборы. Но поскольку сортируется набор из <texdpi="130">n</tex> элементов в наборы размера <texdpi="130">\sqrt{n}</tex>, то проблем быть не должно.
===Алгоритм сортировки===
Algorithm Sort(<texdpi="130">k \log\log n</tex>, <texdpi="130">level</tex>, <texdpi="130">a_{0}</tex>, <texdpi="130">a_{1}</tex>, <texdpi="130">\ldots</tex>, <texdpi="130">a_{t}</tex>)
<texdpi="130">k \log\log n</tex> {{---}} это неконсервативное преимущество, <texdpi="130">a_{i}</tex>-ые это входящие целые числа в наборе, которые надо отсортировать, <texdpi="130">level</tex> это уровень рекурсии.
# Если <texdpi="130">(level == 1)</tex> тогда изучаем размер набора. Если размер меньше или равен <texdpi="130">\sqrt{n}</tex>, то <texdpi="130">return</tex>. Иначе делим этот набор в <texdpi="130">\leqslant</tex> 3 набора, используя [[#lemma2|лемму №2]], чтобы найти медиану, а затем используем [[#lemma5|лемму №5]] для сортировки. Для набора, где все элементы равны медиане, не рассматриваем текущий блок и текущим блоком делаем следующий. Создаем маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направляем маркер для каждого числа назад к месту, где число находилось в начале. Также направляем двубитное число для каждого входного числа, указывающее на текущий блок.# От <texdpi="130">u = 1</tex> до <texdpi="130">k</tex>## Упаковываем <texdpi="130">a^{(u)}_{i}</tex>-ый в часть из <texdpi="130">1/k</tex>-ых номеров контейнеров. Где <texdpi="130">a^{(u)}_{i}</tex> содержит несколько непрерывных блоков, которые состоят из <texdpi="150">\frac{1}{k}</tex>-ых битов <texdpi="130">a_{i}</tex>. При этом у <texdpi="130">a^{(u)}_{i}</tex> текущий блок это самый крупный блок.## Вызываем Sort(<texdpi="130">k \log\log n</tex>, <texdpi="130">level - 1</tex>, <texdpi="130">a^{(u)}_{0}</tex>, <texdpi="130">a^{(u)}_{1}</tex>, <texdpi="130">\ldots</tex>, <texdpi="130">a^{(u)}_{t}</tex>). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к какому набору это число относится, уже направлен назад к месту, где число находится во входных данных. Число, имеющее наибольшее число бит в <texdpi="130">a_{i}</tex>, показывающее на текущий блок в нем, так же направлено назад к <texdpi="130">a_{i}</tex>.## Отправляем <texdpi="130">a_{i}</tex>-ые к их наборам, используя [[#lemma5|лемму №5]].
Algorithm IterateSort
Call Sort(<texdpi="130">k \log\log n</tex>, <texdpi="130">\log_{k}((\log n)/4)</tex>, <texdpi="130">a_{0}</tex>, <texdpi="130">a_{1}</tex>, <texdpi="130">\ldots</tex>, <texdpi="130">a_{n - 1}</tex>);
от 1 до 5
# Помещаем <texdpi="130">a_{i}</tex> в соответствующий набор с помощью bucket sort, потому что наборов около <texdpi="130">\sqrt{n}</tex>.# Для каждого набора <texdpi="130">S = </tex>{<texdpi="130">a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>}, если <texdpi="130">t > \sqrt{n}</tex>, вызываем Sort(<texdpi="130">k \log\log n</tex>, <texdpi="150">\log_{k}(\frac{\log n}{4})</tex>, <texdpi="130">a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}</tex>).
Время работы алгоритма <texdpi="150">O(\frac{n \log\log n}{\log k})</tex>, что доказывает лемму.
}}
==Уменьшение числа бит в числах==
Один из способов ускорить сортировку {{---}} уменьшить число бит в числе. Один из способов уменьшить число бит в числе {{---}} использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий <texdpi="130">O(m)</tex> памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до <texdpi="130">O(n)</tex>. Для того чтобы еще ускорить алгоритм, необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хеширование для всех чисел, хранимых в контейнере. Для этого используется хеш-функция для хеширования <texdpi="130">n</tex> чисел в таблицу размера <texdpi="130">O(n^2)</tex> за константное время без коллизий. Для этого используется модифицированная хеш-функция авторства: Dierzfelbinger и Raman.
Алгоритм: Пусть целое число <texdpi="130">b \geqslant 0</tex> и пусть <texdpi="130">U = \{0, \ldots, 2^b - 1\}</tex>. Класс <texdpi="130">H_{b,s}</tex> хеш-функций из <texdpi="130">U</tex> в <texdpi="130">\{0, \ldots, 2^s - 1\}</tex> определен как <texdpi="130">H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\bmod 2)\}</tex> и для всех <texdpi="130">x</tex> из <texdpi="130">U</tex>: <texdpi="130">h_{a}(x) = (ax</tex> <texdpi="130">\bmod</tex> <texdpi="130">2^b)</tex> <texdpi="130">div</tex> <texdpi="130">2^{b - s}</tex>.
Данный алгоритм базируется на [[#lemma1|лемме №1]].
Взяв <texdpi="130">s = 2 \log n</tex>, получаем хеш-функцию <texdpi="130">h_{a}</tex>, которая захеширует <texdpi="130">n</tex> чисел из <texdpi="130">U</tex> в таблицу размера <texdpi="130">O(n^2)</tex> без коллизий. Очевидно, что <texdpi="130">h_{a}(x)</tex> может быть посчитана для любого <texdpi="130">x</tex> за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, то можно применить <texdpi="130">h_{a}</tex> ко всему контейнеру, и в результате все хеш-значения для всех чисел в контейнере будут посчитаны. Заметим, что это возможно только потому, что в вычисление хеш-значения вовлечены только (<texdpi="130">\bmod</tex> <texdpi="130">2^b</tex>) и (<texdpi="130">div</tex> <texdpi="130">2^{b - s}</tex>).
Такая хеш-функция может быть найдена за <texdpi="130">O(n^3)</tex>.
Следует отметить, что, несмотря на размер таблицы <texdpi="130">O(n^2)</tex>, потребность в памяти не превышает <texdpi="130">O(n)</tex>, потому что хеширование используется только для уменьшения количества бит в числе.
==Signature sorting==
Предположим, что <texdpi="130">n</tex> чисел должны быть отсортированы, и в каждом <texdpi="130">\log m</tex> бит. Будем считаемсчитать, что в каждом числе есть <texdpi="130">h</tex> сегментов, в каждом из которых <texdpi="130">\log m/h</tex> бит. Теперь применяем хеширование ко всем сегментам и получаем <texdpi="130">2h \log n</tex> бит хешированных значений для каждого числа. После сортировки на хешированных значениях для всех начальных чисел начальная задача по сортировке <texdpi="130">n</tex> чисел по <texdpi="130">\log m</tex> бит в каждом стала задачей по сортировке <texdpi="130">n</tex> чисел по <texdpi="130">\log m/h</tex> бит в каждом.
Также рассмотрим проблему последующего разделения. Пусть <texdpi="130">a_{1}</tex>, <texdpi="130">a_{2}</tex>, <texdpi="130">\ldots</tex>, <texdpi="130">a_{p}</tex> {{---}} <texdpi="130">p</tex> чисел и <texdpi="130">S</tex> {{---}} множество чисeл. Необходимо разделить <texdpi="130">S</tex> в <texdpi="130">p + 1</tex> наборов, таких, что: <texdpi="130">S_{0} < a_{1} < S_{1} < a_{2} < \ldots < a_{p} < S_{p}</tex>. Так как используется '''signature sorting''' то перед тем, как делать вышеописанное разделение, необходимо поделить биты в <texdpi="130">a_{i}</tex> на <texdpi="130">h</tex> сегментов и взять некоторые из них. Так же делим биты для каждого числа из <texdpi="130">S</tex> и оставляем только один в каждом числе. По существу, для каждого <texdpi="130">a_{i}</tex> берутся все <texdpi="130">h</tex> сегментов. Если соответствующие сегменты <texdpi="130">a_{i}</tex> и <texdpi="130">a_{j}</tex> совпадают, то нам понадобится только один. Сегмент, который берется для числа в <texdpi="130">S</tex> это сегмент, который выделяется из <texdpi="130">a_{i}</tex>. Таким образом, начальная задача о разделении <texdpi="130">n</tex> чисел по <texdpi="130">\log m</tex> бит преобразуется в несколько задач на разделение с числами по <texdpi="150">\frac{\log m}{h}</tex> бит.
'''Пример''':
<texdpi="130">a_{1} = 3, a_{2} = 5, a_{3} = 7, a_{4} = 10, S = \{1, 4, 6, 8, 9, 13, 14\}</tex>.
Делим числа на 2 два сегмента. Для <texdpi="130">a_{1}</tex> получим верхний сегмент <tex dpi="130">0</tex>, нижний <tex dpi="130">3</tex>; <texdpi="130">a_{2}</tex> {{---}} верхний <tex dpi="130">1</tex>, нижний <tex dpi="130">1</tex>; <texdpi="130">a_{3}</tex> {{---}} верхний <tex dpi="130">1</tex>, нижний <tex dpi="130">3</tex>; <texdpi="130">a_{4}</tex> {{---}} верхний <tex dpi="130">2</tex>, нижний <tex dpi="130">2</tex>. Для элементов из S получим: для <tex dpi="130">1 </tex> нижний <tex dpi="130">1</tex>, так как он выделяется из нижнего сегмента <texdpi="130">a_{1}</tex>; для <tex dpi="130">4 </tex> нижний <tex dpi="130">0</tex>; для <tex dpi="130">8 </tex> нижний <tex dpi="130">0</tex>; для <tex dpi="130">9 </tex> нижний <tex dpi="130">1</tex>; для <tex dpi="130">13 </tex> верхний <tex dpi="130">3</tex>; для <tex dpi="130">14 </tex> верхний <tex dpi="130">3</tex>. Теперь все верхние сегменты, нижние сегменты <tex dpi="130">1 </tex> и <tex dpi="130">3</tex>, нижние сегменты <tex dpi="130">4, 5, 6, 7, </tex> нижние сегменты <tex dpi="130">8, 9, 10 </tex> формируют <tex dpi="130">4 </tex> новые задачи на разделение.
Использование '''signature sorting''' в данном алгоритме:
Есть набор <texdpi="130">T</tex> из <texdpi="130">p</tex> чисел, которые отсортированы как <texdpi="130">a_{1}, a_{2}, \ldots, a_{p}</tex>. Используем числа в <texdpi="130">T</tex> для разделения набора <texdpi="130">S</tex> из <texdpi="130">q</tex> чисел <texdpi="130">b_{1}, b_{2}, \ldots, b_{q}</tex> в <texdpi="130">p + 1</tex> наборов <texdpi="130">S_{0}, S_{1}, \ldots, S_{p}</tex>. Пусть <texdpi="150">h = \frac{\log n}{c \log p}</tex> для константы <texdpi="130">c > 1</tex>. (<texdpi="150">\frac{h}{\log\log n \log p}</tex>)-битные числа могут храниться в одном контейнере, содержащим <texdpi="150">\frac{\log n}{c \log\log n}</tex> бит. Сначала рассматриваем биты в каждом <texdpi="130">a_{i}</tex> и каждом <texdpi="130">b_{i}</tex> как сегменты одинаковой длины <texdpi="150">\frac{h} {\log\log n}</tex>. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, числа в этих контейнерах (<texdpi="130">a_{i}</tex>-ом и <texdpi="130">b_{i}</tex>-ом) хешируются, и получается <texdpi="150">\frac{h}{\log\log n}</tex> хешированных значений в одном контейнере. При вычислении хеш-значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хеш-значения считаются за константное время. Затем, посчитав значения, два контейнера объединяем в один. Пусть <texdpi="130">a'_{i}</tex> {{---}} хеш-контейнер для <texdpi="130">a_{i}</tex>, аналогично <texdpi="130">b'_{i}</tex>. В сумме хеш-значения имеют <texdpi="150">\frac{2 \log n}{c \log\log n}</tex> бит, хотя эти значения разделены на сегменты по <texdpi="150">\frac{h}{ \log\log n}</tex> бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в <texdpi="150">\frac{2 \log n}{c \log\log n}</tex> бит. Потом рассматривается каждый хеш-контейнер как число, и эти хеш-контейнеры сортируются за линейное время (сортировка будет рассмотрена чуть позже). После этой сортировки биты в <texdpi="130">a_{i}</tex> и <texdpi="130">b_{i}</tex> разрезаны на <texdpi="150">\frac{\log\log n}{h}</tex> сегментов. Таким образом, получилось дополнительное мультипликативное преимущество в <texdpi="150">\frac{h} {\log\log n}</tex> (additional multiplicative advantage).
После того, как вышеописанный процесс повторится <texdpi="130">g</tex> раз, получится неконсервативное преимущество в <texdpi="150">(\frac{h} {\log\log n})^g</tex> раз, в то время как потрачено только <texdpi="130">O(gqt)</tex> времени, так как каждое многократное деление происходит за линейное время <texdpi="130">O(qt)</tex>.
Хеш-функция, которая используется, находится следующим образом. Будут хешироватся сегменты, <texdpi="150">\frac{\log\log n}{h}</tex>-ые, <texdpi="150">(\frac{\log\log n}{h})^2</tex>-ые, <texdpi="130">\ldots</tex> по счету в числе. Хеш-функцию для <texdpi="150">(\frac{\log\log n}{h})^t</tex>-ых по счету сегментов, получаем нарезанием всех <texdpi="130">p</tex> чисел на <texdpi="150">(\frac{\log\log n}{h})^t</tex> сегментов. Рассматривая каждый сегмент как число, получаем <texdpi="150">p(\frac{\log\log n}{h})^t</tex> чисел. Затем получаем одну хеш-функцию для этих чисел. Так как <texdpi="130">t < \log n</tex>, то получится не более <texdpi="130">\log n</tex> хеш-функций.
Рассмотрим сортировку за линейное время, о которой было упомянуто ранее. Предполагается, что хешированные значения для каждого контейнера упакованы в <texdpi="150">\frac{2 \log n}{c \log\log n}</tex> бит. Есть <texdpi="130">t</tex> наборов, в каждом из которых <texdpi="130">q + p</tex> хешированных контейнеров по <texdpi="150">\frac{2 \log n}{c \log\log n}</tex> бит в каждом. Эти контейнеры должны быть отсортированы в каждом наборе. Комбинируя все хеш-контейнеры в один pool, сортируем следующим образом.
Procedure '''Linear-Time-Sort'''
Входные данные: <texdpi="150">r > = n^{\frac{2}{5}}</tex> чисел <texdpi="130">d_{i}</tex>, <texdpi="130">d_{i}.value</tex> — значение числа <texdpi="130">d_{i}</tex>, в котором <texdpi="150">\frac{2 \log n}{c \log\log n}</tex> бит, <texdpi="130">d_{i}.set</tex> — набор, в котором находится <texdpi="130">d_{i}</tex>. Следует отметить, что всего есть <texdpi="130">t</tex> наборов.
# Сортируем все <texdpi="130">d_{i}</tex> по <texdpi="130">d_{i}.value</tex>, используя bucket sort. Пусть все отртированные отсортированные числа в <texdpi="130">A[1..r]</tex>. Этот шаг занимает линейное время, так как сортируется не менее <texdpi="150">n^{\frac{2}{5}}</tex> чисел.# Помещаем все <texdpi="130">A[j]</tex> в <texdpi="130">A[j].set</tex>.
==Сортировка с использованием O(n log log n) времени и памяти==
Для сортировки <texdpi="130">n</tex> целых чисел в диапазоне <texdpi="130">\{0, 1, \ldots, m - 1\}</tex> предполагается, что в нашем консервативном алгоритме используется контейнер длины <texdpi="130">O(\log (m + n))</tex>. Далее везде считается, что все числа упакованы в контейнеры одинаковой длины.
Берем <texdpi="130">1/e = 5</tex> для ЭП-дерева Андерссона. Следовательно, у корня будет <texdpi="150">n^{\frac{1}{5}}</tex> детей, и каждое ЭП-дерево в каждом ребенке будет иметь <texdpi="150">n^{\frac{4}{5}}</tex> листьев. В отличие от оригинального дерева, за раз вставляется не один элемент, а <texdpi="130">d^2</tex>, где <texdpi="130">d</tex> — количество детей узла дерева, в котором числа должны спуститься вниз. Алгоритм полностью опускает все <texdpi="130">d^2</tex> чисел на один уровень. В корне опускаются <texdpi="150">n^{\frac{2}{5}}</tex> чисел на следующий уровень. После того, как все числа опустились на следующий уровень, они успешно разделились на <texdpi="130">t_{1} = n^{1/5}</tex> наборов <texdpi="130">S_{1}, S_{2}, \ldots, S_{t_{1}}</tex>, в каждом из которых <texdpi="150">n^{\frac{4}{5}}</tex> чисел и <texdpi="130">S_{i} < S_{j}, i < j</tex>. Затем, берутся <texdpi="150">n^{\frac{8}{25}}</tex> чисел из <texdpi="130">S_{i}</tex> и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на <texdpi="150">t_{2} = n^{\frac{1}{5}}n^{\frac{4}{25}} = n^{\frac{9}{25}}</tex> наборов <texdpi="130">T_{1}, T_{2}, \ldots, T_{t_{2}}</tex>, аналогичных наборам <texdpi="130">S_{i}</tex>, в каждом из которых <texdpi="150">n^{\frac{16}{25}}</tex> чисел. Теперь числа опускаются дальше в ЭП-дереве.
Нетрудно заметить, что перебалансирока занимает <texdpi="130">O(n \log\log n)</tex> времени с <texdpi="130">O(n)</tex> времени на уровень, аналогично стандартному ЭП-дереву Андерссона.
Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне <texdpi="130">s</tex>. Имеется <texdpi="150">t = n^{1 - (\frac{4}{5})^sS}</tex> наборов по <texdpi="150">n^{(\frac{4}{5})^sS}</tex> чисел в каждом. Так как каждый узел на данном уровне имеет <texdpi="150">p = n^{\frac{1}{5} \cdot (\frac{4}{5})^sS}</tex> детей, то на <texdpi="130">s + 1</tex> уровень опускаются <texdpi="150">q = n^{\frac{2}{5} \cdot (\frac{4}{5})^sS}</tex> чисел для каждого набора, или всего <texdpi="150">qt \geqslant n^{\frac{2}{5}}</tex> чисел для всех наборов за один раз.
Спуск вниз можно рассматривать как сортировку <texdpi="130">q</tex> чисел в каждом наборе вместе с <texdpi="130">p</tex> числами <texdpi="130">a_{1}, a_{2}, \ldots, a_{p}</tex> из ЭП-дерева, так, что эти <texdpi="130">q</tex> чисел разделены в <texdpi="130">p + 1</tex> наборов <texdpi="130">S_{0}, S_{1}, \ldots, S_{p}</tex> таких, что <texdpi="130">S_{0} < a_{1} < \ldots < a_{p} < S_{p}</tex>.
Так как <texdpi="130">q</tex> чисел не надо полностью сортировать и <texdpi="130">q = p^2</tex>, то можно использовать [[#lemma6|лемму №6]] для сортировки. Для этого необходимо неконсервативное преимущество, которое получается с помощью [[Сортировка Хана#Signature sorting|signature sorting]]. Для этого используется линейная техника многократного деления (multi-dividing technique).
После <texdpi="130">g</tex> сокращений бит в [[Сортировка Хана#Signature sorting|signature sorting]] получаем неконсервативное преимущество в <texdpi="150">(\frac{h}{ \log\log n})^g</tex>. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на [[#lemma6|лемму №6]] для завершения разделения <texdpi="130">q</tex> чисел с помощью <texdpi="130">p</tex> чисел на наборы. Заметим, что по природе битового сокращения начальная задача разделения для каждого набора перешла в <texdpi="130">w</tex> подзадач разделения на <texdpi="130">w</tex> поднаборов для какого-то числа <texdpi="130">w</tex>.
Теперь для каждого набора все его поднаборы в подзадачах собираются в один набор. Затем, используя [[#lemma6|лемму №6]], делается разделение. Так как получено неконсервативное преимущество в <texdpi="150">(\frac{h}{\log\log n})^g</tex> и работа происходит на уровнях не ниже, чем <texdpi="130">2 \log\log\log n</tex>, то алгоритм занимает <texdpi="150">O(\frac{qt \log\log n}{g(\log h - \log\log\log n) - \log\log\log n}) = O(\log\log n)</tex> времени.
В итоге разделились <texdpi="130">q</tex> чисел <texdpi="130">p</tex> числами в каждый набор. То есть получилось, что <texdpi="130">S_{0} < e_{1} < S_{1} < \ldots < e_{p} < S_{p}</tex>, где <texdpi="130">e_{i}</tex> {{---}} сегмент <texdpi="130">a_{i}</tex>, полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве <texdpi="130">B</tex> так, что числа в <texdpi="130">S_{i}</tex> предшествуют числам в <texdpi="130">S_{j}</tex> если <texdpi="130">i < j</tex> и <texdpi="130">e_{i}</tex> хранится после <texdpi="130">S_{i - 1}</tex>, но до <texdpi="130">S_{i}</tex>.
Пусть <texdpi="130">B[i]</tex> находится в поднаборе <texdpi="130">B[i].subset</tex>. Чтобы позволить разделению выполниться, для каждого поднабора помещаем все <texdpi="130">B[j]</tex> в <texdpi="130">B[j].subset</tex>.
На это потребуется линейное время и место.
Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере <texdpi="130">\log m \geqslant \log\log\log n</tex>, потому что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть <texdpi="150">\frac{h/ }{\log\log n}</tex> хешированных значений (сегментов) в себе на уровне <texdpi="130">\log h</tex> в ЭП-дереве. Полное число хешированных бит в контейнере равно <texdpi="130">(2 \log n)(c \log\log n)</tex> бит. Хешированные биты в контейнере выглядят как <texdpi="130">0^{i}t_{1}0^{i}t_{2} \ldots t_t</tex><tex dpi="150">_{\frac{h/ }{\log\log n}}</tex>, где <texdpi="130">t_{k}</tex>-ые — хешированные биты, а нули {{---}} это просто нули. Сначала упаковываем <texdpi="130">\log\log n</tex> контейнеров в один и получаем <texdpi="130">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, }</tex><tex dpi="150">_{ \frac{h/ }{\log\log n}}</tex>, где <texdpi="130">t_{i, k}</tex>: элемент с номером <texdpi="130">k = 1, 2, \ldots, </tex><tex dpi="150">\frac{h/ }{\log\log n}</tex> из <texdpi="130">i</tex>-ого контейнера. Используем <texdpi="130">O(\log\log n)</tex> шагов, чтобы упаковать <texdpi="130">w_{1}</tex> в <texdpi="130">w_{2} = 0</tex><tex dpi="150">^{\frac{jh/ }{\log\log n}}</tex><tex dpi="130">t_{1, 1}t_{2, 1} \ldots t_{\log\log n, 1}t_{1, 2}t_{2, 2} \ldots t_{1, }</tex><tex dpi="150">_{ \frac{h/ }{\log\log n}}</tex><tex dpi="130">t_{2, }</tex><tex dpi="150">_{ \frac{h/ }{\log\log n} }</tex><tex dpi="130">\ldots t_{\log\log n, }</tex><tex dpi="150">_{ \frac{h/ }{\log\log n}}</tex>. Теперь упакованные хеш-биты занимают <texdpi="130">2 \log </tex><tex dpi="150">\frac{n/}{c}</tex> бит. Используем <texdpi="130">O(\log\log n)</tex> времени чтобы распаковать <texdpi="130">w_{2}</tex> в <texdpi="130">\log\log n</tex> контейнеров <texdpi="130">w_{3, k} = 0</tex><tex dpi="150">^{\frac{jh/ }{\log\log n}}</tex><tex dpi="130">0^{r}t_{k, 1}0^{r}t_{k, 2} \ldots t_{k, }</tex><tex dpi="150">_{ \frac{h/ }{\log\log n} }</tex> <tex dpi="130">k = 1, 2, \ldots, \log\log n</tex>. Затем, используя <texdpi="130">O(\log\log n)</tex> времени, упаковываем эти <texdpi="130">\log\log n</tex> контейнеров в один <texdpi="130">w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} \ldots t_{1, }</tex><tex dpi="150">_{ \frac{h/ }{\log\log n}}</tex><tex dpi="130">0^{r}t_{2, 1} \ldots t_{\log\log n, }</tex><tex dpi="150">_{ \frac{h/ }{\log\log n}}</tex>. Затем, используя <texdpi="130">O(\log\log n)</tex> шагов, упаковываем <texdpi="130">w_{4}</tex> в <texdpi="130">w_{5} = 0^{s}t_{1, 1}t_{1, 2} \ldots t_{1, }</tex><tex dpi="150">_{ \frac{h/ }{\log\log n}}</tex><tex dpi="130">t_{2, 1}t_{2, 2} \ldots t_{\log\log n, }</tex><tex dpi="150">_{ \frac{h/ }{\log\log n}}</tex>. В итоге используется <texdpi="130">O(\log\log n)</tex> времени для упаковки <texdpi="130">\log\log n</tex> контейнеров. Считаем, что время, потраченное на один контейнер — константа.
==См. также==