Сортировка Хана

Материал из Викиконспекты
Перейти к: навигация, поиск

Сортировка Хана (англ. Hansort) — сложный алгоритм сортировки целых чисел со сложностью O(n \log\log n), где n — количество элементов для сортировки.

Данная статья писалась на основе брошюры Хана (англ. Yijie Han), посвященной этой сортировке.

Содержание

[править] Описание

Алгоритм построен на основе экспоненциального поискового дерева Андерсона (англ. Andersson's exponential search tree). Сортировка происходит за счет вставки целых чисел в экспоненциальное поисковое дерево (далее — ЭП-дерево).

[править] Экспоненциальное поисковое дерево Андерсона

Определение:
ЭП-дерево — это дерево поиска, в котором все ключи хранятся в листьях этого дерева и количество детей у каждого узла уменьшается экспоненциально от глубины узла.


Общая структура ЭП-дерева

Структура ЭП-дерева:

1) Корень имеет \Theta (n^e) сыновей ( 0 < e < 1 ). Все сыновья являются ЭП-деревьями.

2) Каждое поддерево корня имеет \Theta(n^{1-e}) сыновей.

В этом дереве O(n \log\log n) уровней. При нарушении баланса дерева необходимо балансирование, которое требует O(n \log\log n) времени при n вставленных целых числах. Такое время достигается за счет вставки чисел группами, а не поодиночке, как изначально предлагал Андерссон.

[править] Определения

Определение:
Контейнер — объект, в которым хранятся наши данные. Например: 32-битные и 64-битные числа, массивы, вектора.


Определение:
Алгоритм, сортирующий n целых чисел из множества \{0, 1, \ldots, m - 1\}, называется консервативным, если длина контейнера (число бит в контейнере) равна O(\log(m + n)). Если длина больше, то алгоритм неконсервативный.


Определение:
Если сортируются целые числа из множества \{0, 1, \ldots, m - 1\} с длиной контейнера k \log (m + n) с k \geqslant 1, тогда сортировка происходит с неконсервативным преимуществом k.


Определение:
Для множества S определим

\min(S) = \min\limits_{a \in S} a

\max(S) = \max\limits_{a \in S} a

Набор S1 < S2 если \max(S1) \leqslant \min(S2)


Определение:
Предположим, есть набор T из p чисел, которые уже отсортированы как a_{1}, a_{2}, \ldots, a_{p} и набор S из q чисел b_{1}, b_{2}, \ldots, b_{q}. Тогда разделением q чисел p числами называется p + 1 набор S_{0}, S_{1}, \ldots, S_{p}, где S_{0} < a_{1} < S_{1} < \ldots < a_{p} < S_{p}.


[править] Леммы

Лемма (№ 1):
Даны целые числа b \geqslant s \geqslant 0, и T является подмножеством множества \{0, \ldots, 2^b - 1\}, содержащим n элементов, и t \geqslant 2^{-s + 1}С^k_{n}. Функция h_{a}, принадлежащая H_{b,s}, может быть выбрана за время O(bn^2) так, что количество коллизий coll(h_{a}, T) \leqslant t.
Лемма (№ 2):
Выбор s-ого наибольшего числа среди n чисел, упакованных в \frac{n}{g} контейнеров, может быть сделан за время O(\frac{n \log g}{g}) и с использованием O(\frac{n}{g}) памяти. В том числе, так может быть найдена медиана.
Доказательство:
\triangleright
Так как возможно делать попарное сравнение g чисел в одном контейнере с g числами в другом и извлекать большие числа из одного контейнера и меньшие из другого за константное время, возможно упаковать медианы из первого, второго, \ldots, g-ого чисел из 5 контейнеров в один контейнер за константное время. Таким образом, набор S из медиан теперь содержится в \frac{n}{5g} контейнерах. Рекурсивно находим медиану m в S. Используя m, уберем хотя бы \frac{n}{4} чисел среди n. Затем упакуем оставшиеся из \frac{n}{g} контейнеров в \frac{3n}{4g} контейнеров и затем продолжим рекурсию.
\triangleleft
Лемма (№ 3):
Если g целых чисел, в сумме использующих \frac{\log n}{2} бит, упакованы в один контейнер, тогда n чисел в \frac{n}{g} контейнерах могут быть отсортированы за время O(\frac{n \log g}{g}) с использованием O(\frac{n}{g}) памяти.
Доказательство:
\triangleright

Так как используется только \frac{\log n}{2} бит в каждом контейнере для хранения g чисел, используем bucket sort, чтобы отсортировать все контейнеры, представляя каждый как число, что занимает O(\frac{n}{g}) времени и памяти. Так как используется \frac{\log n}{2} бит на контейнер, понадобится \sqrt{n} шаблонов для всех контейнеров. Затем поместим g < \frac{\log n}{2} контейнеров с одинаковым шаблоном в одну группу. Для каждого шаблона останется не более g - 1 контейнеров, которые не смогут образовать группу. Поэтому не более \sqrt{n}(g - 1) контейнеров не смогут сформировать группу. Для каждой группы помещаем i-е число во всех g контейнерах в один. Таким образом берутся g g-целых векторов и получаются g g-целых векторов, где i-ый вектор содержит i-ое число из входящего вектора. Эта транспозиция может быть сделана за время O(g \log g), с использованием O(g) памяти. Для всех групп это занимает время O(\frac{n \log g}{g}), с использованием O(\frac{n}{g}) памяти.

Для контейнеров вне групп (которых \sqrt{n}(g - 1) штук) разбираем и собираем заново контейнеры. На это потребуется не более O(\frac{n}{g}) времени и памяти. После всего этого используем карманную сортировку вновь для сортировки n контейнеров. Таким образом, все числа отсортированы.


Заметим, что когда g = O( \log n), сортировка O(n) чисел в \frac{n}{g} контейнеров произойдет за время O((\frac{n}{g}) \log\log n) с использованием O(\frac{n}{g}) памяти. Выгода очевидна.
\triangleleft
Лемма (№ 4):
Примем, что каждый контейнер содержит \log m > \log n бит, и g чисел, в каждом из которых \frac{\log m}{g} бит, упакованы в один контейнер. Если каждое число имеет маркер, содержащий \frac{\log n}{2g} бит, и g маркеров упакованы в один контейнер таким же образом^*, что и числа, тогда n чисел в \frac{n}{g} контейнерах могут быть отсортированы по их маркерам за время O(\frac{n \log\log n}{g}) с использованием O(\frac{n}{g}) памяти. (*): если число a упаковано как s-ое число в t-ом контейнере для чисел, тогда маркер для a упакован как s-ый маркер в t-ом контейнере для маркеров.
Доказательство:
\triangleright
Контейнеры для маркеров могут быть отсортированы с помощью bucket sort потому, что каждый контейнер использует \frac{\log n}{2} бит. Сортировка сгруппирует контейнеры для чисел как в лемме №3. Перемещаем каждую группу контейнеров для чисел.
\triangleleft
Лемма (№ 5):
Предположим, что каждый контейнер содержит \log m \log\log n > \log n бит, что g чисел, в каждом из которых \frac{\log m}{g} бит, упакованы в один контейнер, что каждое число имеет маркер, содержащий \frac{\log n}{2g} бит, и что g маркеров упакованы в один контейнер тем же образом что и числа. Тогда n чисел в \frac{n}{g} контейнерах могут быть отсортированы по своим маркерам за время O(\frac{n}{g}) с использованием O(\frac{n}{g}) памяти.
Доказательство:
\triangleright

Заметим, что несмотря на то, что длина контейнера \log m \log\log n бит, всего \log m бит используется для хранения упакованных чисел. Так же как в лемме №3 и лемме №4 сортируем контейнеры упакованных маркеров с помощью bucket sort. Для того, чтобы перемещать контейнеры чисел, помещаем g \log\log n вместо g контейнеров чисел в одну группу. Для транспозиции чисел в группе, содержащей g \log\log n контейнеров, упаковываем g \log\log n контейнеров в g, упаковывая \log\log n контейнеров в один. Далее делаем транспозицию над g контейнерами. Таким образом перемещение занимает всего O(g \log\log n) времени для каждой группы и O(\frac{n}{g}) времени для всех чисел. После завершения транспозиции, распаковываем g контейнеров в g \log\log n контейнеров.


Заметим, что если длина контейнера \log m \log\log n и только \log m бит используется для упаковки g \leqslant \log n чисел в один контейнер, тогда выбор в лемме №2 может быть сделан за время и память O(\frac{n}{g}), потому что упаковка в доказательстве лемме №2 теперь может быть сделана за время O(\frac{n}{g}).
\triangleleft


Лемма (№ 6):
n целых чисел можно отсортировать в \sqrt{n} наборов S_{1}, S_{2}, \ldots, S_{\sqrt{n}} таким образом, что в каждом наборе \sqrt{n} чисел и S_{i} < S_{j} при i < j, за время O(\frac{n \log\log n} {\log k}) и место O(n) с неконсервативным преимуществом k \log\log n.
Доказательство:
\triangleright

Алгоритм сортировки n целых чисел в \sqrt{n} наборов, представленный ниже, является доказательством данной леммы.

Постановка задачи и решение некоторых проблем:


Рассмотрим проблему сортировки n целых чисел из множества \{0, 1, \ldots, m - 1\} в \sqrt{n} наборов, как в условии леммы. Предполагаем, что каждый контейнер содержит k \log\log n \log m бит и хранит число в \log m бит. Поэтому неконсервативное преимущество — k \log \log n. Также предполагаем, что \log m \geqslant \log n \log\log n. Иначе можно использовать radix sort для сортировки за время O(n \log\log n) и линейную память. Делим \log m бит, используемых для представления каждого числа, в \log n блоков. Таким образом, каждый блок содержит как минимум \log\log n бит. i-ый блок содержит с \frac{i \log m} {\log n}-ого по (\frac{(i + 1) \log m} {\log n - 1})-ый биты. Биты считаются с наименьшего бита, начиная с нуля. Теперь у нас имеется 2 \log n-уровневый алгоритм, который работает следующим образом:


На каждой стадии работаем с одним блоком бит. Назовем эти блоки маленькими числами (далее м.ч.), потому что каждое м.ч. теперь содержит только \frac{\log m}{\log n} бит. Каждое число представлено и соотносится с м.ч., над которым работаем в данный момент. Положим, что нулевая стадия работает с самым большим блоком (блок номер \log n - 1). Предполагаем, что биты этих м.ч. упакованы в \frac{n}{\log n} контейнеров с \log n м.ч. упакованными в один контейнер. Пренебрегая временем, потраченным на эту упаковку, считаем, что она бесплатна. По лемме №2 находим медиану этих n м.ч. за время и память O(\frac{n}{\log n}). Пусть a — это найденная медиана. Тогда n м.ч. могут быть разделены на не более чем три группы: S_{1}, S_{2} и S_{3}. S_{1} содержит м.ч., которые меньше a, S_{2} содержит м.ч., равные a, S_{3} содержит м.ч., большие a. Также мощность S_{1} и S_{3} не превосходит n/2. Мощность S_{2} может быть любой. Пусть S'_{2} — это набор чисел, у которых наибольший блок находится в S_{2}. Тогда убираем из дальнейшего рассмотрения \frac{\log m}{\log n} бит (наибольший блок) из каждого числа, принадлежащего S'_{2}. Таким образом, после первой стадии каждое число находится в наборе размера не большего половины размера начального набора или один из блоков в числе убран из дальнейшего рассмотрения. Так как в каждом числе только \log n блоков, для каждого числа потребуется не более \log n стадий, чтобы поместить его в набор половинного размера. За 2 \log n стадий все числа будут отсортированы. Так как на каждой стадии работаем с \frac{n}{\log n} контейнерами, то игнорируя время, необходимое на упаковку м.ч. в контейнеры и помещение м.ч. в нужный набор, затрачивается O(n) времени из-за 2 \log n стадий.


Сложная часть алгоритма заключается в том, как поместить м.ч. в набор, которому принадлежит соответствующее число, после предыдущих операций деления набора в нашем алгоритме. Предположим, что n чисел уже поделены в e наборов. Используем \log e битов чтобы сделать марки для каждого набора. Теперь используем лемме №5. Полный размер маркера для каждого контейнера должен быть \frac{\log n}{2}, и маркер использует \log e бит, значит количество маркеров g в каждом контейнере должно быть не более \frac{\log n}{2\log e}. В дальнейшем, так как g = \frac{\log n}{2 \log e}, м.ч. должны влезать в контейнер. Каждый контейнер содержит k \log\log n \log n блоков, каждое м.ч. может содержать O(\frac{k \log n}{g}) = O(k \log e) блоков. Заметим, что используется неконсервативное преимущество в \log\log n для лемме №5 Поэтому предполагается, что \frac{\log n}{2 \log e} м.ч., в каждом из которых k \log e блоков битов числа, упакованны в один контейнер. Для каждого м.ч. используется маркер из \log e бит, который показывает, к какому набору он принадлежит. Предполагаем, что маркеры так же упакованы в контейнеры, как и м.ч. Так как каждый контейнер для маркеров содержит \frac{\log n}{2 \log e} маркеров, то для каждого контейнера требуется \frac{\log n}{2} бит. Таким образом, лемма №5 может быть применена для помещения м.ч. в наборы, которым они принадлежат. Так как используется O(\frac{n \log e}{ \log n}) контейнеров, то время, необходимое для помещения м.ч. в их наборы, равно O(\frac{n \log e}{ \log n}).

Стоит отметить, что процесс помещения нестабилен, т.к. основан на алгоритме из леммы №5.


При таком помещении сразу возникает следующая проблема.

Рассмотрим число a, которое является i-ым в наборе S. Рассмотрим блок a (назовем его a'), который является i-ым м.ч. в S. Когда используется вышеописанный метод перемещения нескольких следующих блоков a (назовем это a'') в S, a'' просто перемещен на позицию в наборе S, но не обязательно на позицию i (где расположен a'). Если значение блока a' одинаково для всех чисел в S, то это не создаст проблемы потому, что блок одинаков вне зависимости от того в какое место в S помещен a''. Иначе у нас возникает проблема дальнейшей сортировки. Поэтому поступаем следующим образом: На каждой стадии числа в одном наборе работают на общем блоке, который назовем "текущий блок набора". Блоки, которые предшествуют текущему блоку содержат важные биты и идентичны для всех чисел в наборе. Когда помещаем больше бит в набор, последующие блоки помещаются в набор вместе с текущим блоком. Так вот, в вышеописанном процессе помещения предполагается, что самый значимый блок среди k \log e блоков — это текущий блок. Таким образом, после того, как эти k \log e блоков помещены в набор, изначальный текущий блок удаляется, потому что известно, что эти k \log e блоков перемещены в правильный набор, и нам не важно где находился начальный текущий блок. Тот текущий блок находится в перемещенных k \log e блоках.


Стоит отметить, что после нескольких уровней деления размер наборов станет маленьким. Леммы 3, 4, 5 расчитаны на не очень маленькие наборы. Но поскольку сортируется набор из n элементов в наборы размера \sqrt{n}, то проблем быть не должно.


Алгоритм сортировки

Algorithm Sort(advantage, level, a_{0}, a_{1}, \ldots, a_{t})

advantage — это неконсервативное преимущество равное k\log\log n, a_{i}-ые это входящие целые числа в наборе, которые надо отсортировать, level это уровень рекурсии.

  1. Если level равен 1 тогда изучаем размер набора. Если размер меньше или равен \sqrt{n}, то return. Иначе делим этот набор в \leqslant 3 набора, используя лемму №2, чтобы найти медиану, а затем используем лемму №5 для сортировки. Для набора, где все элементы равны медиане, не рассматриваем текущий блок и текущим блоком делаем следующий. Создаем маркер, являющийся номером набора для каждого из чисел (0, 1 или 2). Затем направляем маркер для каждого числа назад к месту, где число находилось в начале. Также направляем двубитное число для каждого входного числа, указывающее на текущий блок.
  2. От u = 1 до k
    1. Упаковываем a^{(u)}_{i}-ый в часть из 1/k-ых номеров контейнеров. Где a^{(u)}_{i} содержит несколько непрерывных блоков, которые состоят из \frac{1}{k}-ых битов a_{i}. При этом у a^{(u)}_{i} текущий блок это самый крупный блок.
    2. Вызываем Sort(advantage, level - 1, a^{(u)}_{0}, a^{(u)}_{1}, \ldots, a^{(u)}_{t}). Когда алгоритм возвращается из этой рекурсии, маркер, показывающий для каждого числа, к какому набору это число относится, уже направлен назад к месту, где число находится во входных данных. Число, имеющее наибольшее число бит в a_{i}, показывающее на текущий блок в нем, так же направлено назад к a_{i}.
    3. Отправляем a_{i}-ые к их наборам, используя лемму №5.

Algorithm IterateSort Call Sort(advantage, \log_{k}((\log n)/4), a_{0}, a_{1}, \ldots, a_{n - 1});

от 1 до 5

  1. Помещаем a_{i} в соответствующий набор с помощью блочной сортировки (англ. bucket sort), потому что наборов около \sqrt{n}.
  2. Для каждого набора S ={a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}}, если t > \sqrt{n}, вызываем Sort(advantage, \log_{k}(\frac{\log n}{4}), a_{i_{0}}, a_{i_{1}}, \ldots, a_{i_{t}}).
Время работы алгоритма O(\frac{n \log\log n}{\log k}), что доказывает лемму.
\triangleleft


[править] Уменьшение числа бит в числах

Один из способов ускорить сортировку — уменьшить число бит в числе. Один из способов уменьшить число бит в числе — использовать деление пополам (эту идею впервые подал van Emde Boas). Деление пополам заключается в том, что количество оставшихся бит в числе уменьшается в 2 раза. Это быстрый способ, требующий O(m) памяти. Для своего дерева Андерссон использует хеширование, что позволяет сократить количество памяти до O(n). Для того чтобы еще ускорить алгоритм, необходимо упаковать несколько чисел в один контейнер, чтобы затем за константное количество шагов произвести хеширование для всех чисел, хранимых в контейнере. Для этого используется хеш-функция для хеширования n чисел в таблицу размера O(n^2) за константное время без коллизий. Для этого используется модифицированная хеш-функция авторства: Dierzfelbinger и Raman.


Алгоритм: Пусть целое число b \geqslant 0 и пусть U = \{0, \ldots, 2^b - 1\}. Класс H_{b,s} хеш-функций из U в \{0, \ldots, 2^s - 1\} определен как H_{b,s} = \{h_{a} \mid 0 < a < 2^b, a \equiv 1 (\bmod 2)\} и для всех x из U: h_{a}(x) = (ax \bmod 2^b) div 2^{b - s}.

Данный алгоритм базируется на лемме №1.


Взяв s = 2 \log n, получаем хеш-функцию h_{a}, которая захеширует n чисел из U в таблицу размера O(n^2) без коллизий. Очевидно, что h_{a}(x) может быть посчитана для любого x за константное время. Если упаковать несколько чисел в один контейнер так, что они разделены несколькими битами нулей, то можно применить h_{a} ко всему контейнеру, и в результате все хеш-значения для всех чисел в контейнере будут посчитаны. Заметим, что это возможно только потому, что в вычисление хеш-значения вовлечены только (\bmod 2^b) и (div 2^{b - s}).


Такая хеш-функция может быть найдена за O(n^3).

Следует отметить, что, несмотря на размер таблицы O(n^2), потребность в памяти не превышает O(n), потому что хеширование используется только для уменьшения количества бит в числе.

[править] Сортировка по ключу

Предположим, что n чисел должны быть отсортированы, и в каждом \log m бит. Будем считать, что в каждом числе есть h сегментов, в каждом из которых \log \frac{m}{h} бит. Теперь применяем хеширование ко всем сегментам и получаем 2h \log n бит хешированных значений для каждого числа. После сортировки на хешированных значениях для всех начальных чисел начальная задача по сортировке n чисел по \log m бит в каждом стала задачей по сортировке n чисел по \log \frac{m}{h} бит в каждом.


Также рассмотрим проблему последующего разделения. Пусть a_{1}, a_{2}, \ldots, a_{p}p чисел и S — множество чисeл. Необходимо разделить S в p + 1 наборов, таких, что: S_{0} < a_{1} < S_{1} < a_{2} < \ldots < a_{p} < S_{p}. Так как используется сортировка по ключу (англ. signature sorting) то перед тем, как делать вышеописанное разделение, необходимо поделить биты в a_{i} на h сегментов и взять некоторые из них. Так же делим биты для каждого числа из S и оставляем только один в каждом числе. По существу, для каждого a_{i} берутся все h сегментов. Если соответствующие сегменты a_{i} и a_{j} совпадают, то нам понадобится только один. Сегмент, который берется для числа в S это сегмент, который выделяется из a_{i}. Таким образом, начальная задача о разделении n чисел по \log m бит преобразуется в несколько задач на разделение с числами по \frac{\log m}{h} бит.


Пример:

Han-example.png

a_{1} = 3, a_{2} = 5, a_{3} = 7, a_{4} = 10, S = \{1, 4, 6, 8, 9, 13, 14\}.

Делим числа на два сегмента. Для a_{1} получим верхний сегмент 0, нижний 3; a_{2} — верхний 1, нижний 1; a_{3} — верхний 1, нижний 3; a_{4} — верхний 2, нижний 2. Для элементов из S получим: для 1 нижний 1, так как он выделяется из нижнего сегмента a_{1}; для 4 нижний 0; для 8 нижний 0; для 9 нижний 1; для 13 верхний 3; для 14 верхний 3. Теперь все верхние сегменты, нижние сегменты 1 и 3, нижние сегменты 4, 5, 6, 7, нижние сегменты 8, 9, 10 формируют 4 новые задачи на разделение.


Использование сортировки по ключу в данном алгоритме:

Есть набор T из p чисел, которые отсортированы как a_{1}, a_{2}, \ldots, a_{p}. Используем числа в T для разделения набора S из q чисел b_{1}, b_{2}, \ldots, b_{q} в p + 1 наборов S_{0}, S_{1}, \ldots, S_{p}. Пусть h = \frac{\log n}{c \log p} для константы c > 1. (\frac{h}{\log\log n \log p})-битные числа могут храниться в одном контейнере, содержащим \frac{\log n}{c \log\log n} бит. Сначала рассматриваем биты в каждом a_{i} и каждом b_{i} как сегменты одинаковой длины \frac{h} {\log\log n}. Рассматриваем сегменты как числа. Чтобы получить неконсервативное преимущество для сортировки, числа в этих контейнерах (a_{i}-ом и b_{i}-ом) хешируются, и получается \frac{h}{\log\log n} хешированных значений в одном контейнере. При вычислении хеш-значений сегменты не влияют друг на друга, можно даже отделить четные и нечетные сегменты в два контейнера. Не умаляя общности считаем, что хеш-значения считаются за константное время. Затем, посчитав значения, два контейнера объединяем в один. Пусть a'_{i} — хеш-контейнер для a_{i}, аналогично b'_{i}. В сумме хеш-значения имеют \frac{2 \log n}{c \log\log n} бит, хотя эти значения разделены на сегменты по \frac{h}{ \log\log n} бит в каждом контейнере. Между сегментами получаются пустоты, которые забиваются нулями. Сначала упаковываются все сегменты в \frac{2 \log n}{c \log\log n} бит. Потом рассматривается каждый хеш-контейнер как число, и эти хеш-контейнеры сортируются за линейное время (сортировка будет рассмотрена чуть позже). После этой сортировки биты в a_{i} и b_{i} разрезаны на \frac{\log\log n}{h} сегментов. Таким образом, получилось дополнительное мультипликативное преимущество (англ. additional multiplicative advantage) в \frac{h} {\log\log n}.

После того, как вышеописанный процесс повторится g раз, получится неконсервативное преимущество в (\frac{h} {\log\log n})^g раз, в то время как потрачено только O(gqt) времени, так как каждое многократное деление происходит за линейное время O(qt).


Хеш-функция, которая используется, находится следующим образом. Будут хешироватся сегменты, \frac{\log\log n}{h}-ые, (\frac{\log\log n}{h})^2-ые, \ldots по счету в числе. Хеш-функцию для (\frac{\log\log n}{h})^t-ых по счету сегментов, получаем нарезанием всех p чисел на (\frac{\log\log n}{h})^t сегментов. Рассматривая каждый сегмент как число, получаем p(\frac{\log\log n}{h})^t чисел. Затем получаем одну хеш-функцию для этих чисел. Так как t < \log n, то получится не более \log n хеш-функций.


Рассмотрим сортировку за линейное время, о которой было упомянуто ранее. Предполагается, что хешированные значения для каждого контейнера упакованы в \frac{2 \log n}{c \log\log n} бит. Есть t наборов, в каждом из которых q + p хешированных контейнеров по \frac{2 \log n}{c \log\log n} бит в каждом. Эти контейнеры должны быть отсортированы в каждом наборе. Комбинируя все хеш-контейнеры в один pool, сортируем следующим образом.


Операция сортировки за линейное время (англ. Linear-Time-Sort)

Входные данные: r \geqslant n^{\frac{2}{5}} чисел d_{i}, d_{i}.value — значение числа d_{i}, в котором \frac{2 \log n}{c \log\log n} бит, d_{i}.set — набор, в котором находится d_{i}. Следует отметить, что всего есть t наборов.

  1. Сортируем все d_{i} по d_{i}.value, используя bucket sort. Пусть все отсортированные числа в A[1..r]. Этот шаг занимает линейное время, так как сортируется не менее n^{\frac{2}{5}} чисел.
  2. Помещаем все A[j] в A[j].set.

[править] Сортировка с использованием O(n log log n) времени и памяти

Для сортировки n целых чисел в диапазоне \{0, 1, \ldots, m - 1\} предполагается, что в нашем консервативном алгоритме используется контейнер длины O(\log (m + n)). Далее везде считается, что все числа упакованы в контейнеры одинаковой длины.


Берем 1/e = 5 для ЭП-дерева Андерссона. Следовательно, у корня будет n^{\frac{1}{5}} детей, и каждое ЭП-дерево в каждом ребенке будет иметь n^{\frac{4}{5}} листьев. В отличие от оригинального дерева, за раз вставляется не один элемент, а d^2, где d — количество детей узла дерева, в котором числа должны спуститься вниз. Алгоритм полностью опускает все d^2 чисел на один уровень. В корне опускаются n^{\frac{2}{5}} чисел на следующий уровень. После того, как все числа опустились на следующий уровень, они успешно разделились на t_{1} = n^{1/5} наборов S_{1}, S_{2}, \ldots, S_{t_{1}}, в каждом из которых n^{\frac{4}{5}} чисел и S_{i} < S_{j}, i < j. Затем, берутся n^{\frac{8}{25}} чисел из S_{i} и опускаются на следующий уровень ЭП-дерева. Это повторяется, пока все числа не опустятся на следующий уровень. На этом шаге числа разделены на t_{2} = n^{\frac{1}{5}}n^{\frac{4}{25}} = n^{\frac{9}{25}} наборов T_{1}, T_{2}, \ldots, T_{t_{2}}, аналогичных наборам S_{i}, в каждом из которых n^{\frac{16}{25}} чисел. Теперь числа опускаются дальше в ЭП-дереве.

Нетрудно заметить, что перебалансирока занимает O(n \log\log n) времени с O(n) времени на уровень, аналогично стандартному ЭП-дереву Андерссона.


Нам следует нумеровать уровни ЭП-дерева с корня, начиная с нуля. Рассмотрим спуск вниз на уровне s. Имеется t = n^{1 - (\frac{4}{5})^S} наборов по n^{(\frac{4}{5})^S} чисел в каждом. Так как каждый узел на данном уровне имеет p = n^{\frac{1}{5} \cdot (\frac{4}{5})^S} детей, то на s + 1 уровень опускаются q = n^{\frac{2}{5} \cdot (\frac{4}{5})^S} чисел для каждого набора, или всего qt \geqslant n^{\frac{2}{5}} чисел для всех наборов за один раз.


Спуск вниз можно рассматривать как сортировку q чисел в каждом наборе вместе с p числами a_{1}, a_{2}, \ldots, a_{p} из ЭП-дерева, так, что эти q чисел разделены в p + 1 наборов S_{0}, S_{1}, \ldots, S_{p} таких, что S_{0} < a_{1} < \ldots < a_{p} < S_{p}.


Так как q чисел не надо полностью сортировать и q = p^2, то можно использовать лемму №6 для сортировки. Для этого необходимо неконсервативное преимущество, которое получается с помощью signature sorting. Для этого используется линейная техника многократного деления (англ. multi-dividing technique).


После g сокращений бит в signature sorting получаем неконсервативное преимущество в (\frac{h}{ \log\log n})^g. Мы не волнуемся об этих сокращениях до конца потому, что после получения неконсервативного преимущества мы можем переключиться на лемму №6 для завершения разделения q чисел с помощью p чисел на наборы. Заметим, что по природе битового сокращения начальная задача разделения для каждого набора перешла в w подзадач разделения на w поднаборов для какого-то числа w.


Теперь для каждого набора все его поднаборы в подзадачах собираются в один набор. Затем, используя лемму №6, делается разделение. Так как получено неконсервативное преимущество в (\frac{h}{\log\log n})^g и работа происходит на уровнях не ниже, чем 2 \log\log\log n, то алгоритм занимает O(\frac{qt \log\log n}{g(\log h - \log\log\log n) - \log\log\log n}) = O(\log\log n) времени.


В итоге разделились q чисел p числами в каждый набор. То есть получилось, что S_{0} < e_{1} < S_{1} < \ldots < e_{p} < S_{p}, где e_{i} — сегмент a_{i}, полученный с помощью битового сокращения. Такое разделение получилось комбинированием всех поднаборов в подзадачах. Предполагаем, что числа хранятся в массиве B так, что числа в S_{i} предшествуют числам в S_{j} если i < j и e_{i} хранится после S_{i - 1}, но до S_{i}.


Пусть B[i] находится в поднаборе B[i].subset. Чтобы позволить разделению выполниться, для каждого поднабора помещаем все B[j] в B[j].subset.

На это потребуется линейное время и место.


Теперь рассмотрим проблему упаковки, которая решается следующим образом. Считается, что число бит в контейнере \log m \geqslant \log\log\log n, потому что в противном случае можно использовать radix sort для сортировки чисел. У контейнера есть \frac{h}{\log\log n} хешированных значений (сегментов) в себе на уровне \log h в ЭП-дереве. Полное число хешированных бит в контейнере равно (2 \log n)(c \log\log n) бит. Хешированные биты в контейнере выглядят как 0^{i}t_{1}0^{i}t_{2} \ldots t_{\frac{h}{\log\log n}}, где t_{k}-ые — хешированные биты, а нули — это просто нули. Сначала упаковываем \log\log n контейнеров в один и получаем 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,}_{  \frac{h}{\log\log n}}, где t_{i, k}: элемент с номером k = 1, 2,  \ldots,\frac{h}{\log\log n} из i-ого контейнера. Используем O(\log\log n) шагов, чтобы упаковать w_{1} в w_{2} = 0^{\frac{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,}_{ \frac{h}{\log\log n}}t_{2,}_{ \frac{h}{\log\log n}}\ldots t_{\log\log n,}_{ \frac{h}{\log\log n}}. Теперь упакованные хеш-биты занимают 2 \log\frac{n}{c} бит. Используем O(\log\log n) времени чтобы распаковать w_{2} в \log\log n контейнеров w_{3, k} = 0^{\frac{jh}{\log\log n}}0^{r}t_{k, 1}0^{r}t_{k, 2} \ldots t_{k,}_{ \frac{h}{\log\log n}} k = 1, 2, \ldots, \log\log n. Затем, используя O(\log\log n) времени, упаковываем эти \log\log n контейнеров в один w_{4} = 0^{r}t_{1, 1}0^{r}t_{1, 2} \ldots t_{1,}_{ \frac{h}{\log\log n}}0^{r}t_{2, 1} \ldots t_{\log\log n,}_{ \frac{h}{\log\log n}}. Затем, используя O(\log\log n) шагов, упаковываем w_{4} в w_{5} = 0^{s}t_{1, 1}t_{1, 2} \ldots t_{1,}_{ \frac{h}{\log\log n}}t_{2, 1}t_{2, 2} \ldots t_{\log\log n,}_{ \frac{h}{\log\log n}}. В итоге используется O(\log\log n) времени для упаковки \log\log n контейнеров. Считаем, что время, потраченное на один контейнер — константа.

[править] См. также

[править] Источники информации

Личные инструменты
Пространства имён
Варианты
Действия
Навигация
Инструменты