Быстрый поиск наибольшей возрастающей подпоследовательности — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
м (Обработка блока: Дополнение про predecessor)
м (Тире, to, dfrac, radix interwiki, точки, geqslant, неясность некоторых формулировок, tex в заголовках, см также, источники, категории)
Строка 6: Строка 6:
  
 
[[Файл:Task.jpg]]
 
[[Файл:Task.jpg]]
== Алгоритм <tex>O(n\operatorname{log}\operatorname{log}n)</tex> ==
+
== Алгоритм O(n log log n) ==
 
=== Нахождение длины НВП ===
 
=== Нахождение длины НВП ===
 
==== Основная идея ====
 
==== Основная идея ====
Строка 17: Строка 17:
 
Будем последовательно обрабатывать элементы <tex>\pi(1), \pi(2),~\dots,~\pi(n)</tex>:
 
Будем последовательно обрабатывать элементы <tex>\pi(1), \pi(2),~\dots,~\pi(n)</tex>:
  
* Если <tex>\pi(i)</tex> больше {{Acronym | <tex>\pi(1), \pi(2),~\dots~,\pi(i-1)</tex> | всех уже полученных значений B }}, значит с ним можно сделать максимальную, из уже рассмотренных, возрастающую подпоследовательность. Записываем его в конец <tex>B</tex>
+
* Если <tex>\pi(i)</tex> больше {{Acronym | <tex>\pi(1), \pi(2),~\dots~,\pi(i-1)</tex> | всех уже полученных значений B }}, значит с ним можно сделать максимальную, из уже рассмотренных, возрастающую подпоследовательность. Записываем его в конец <tex>B</tex>.
 
* Иначе <tex>\pi(i)</tex> заменяет наименьший лучший элемент, из тех, что больше <tex>\pi(i)</tex>.
 
* Иначе <tex>\pi(i)</tex> заменяет наименьший лучший элемент, из тех, что больше <tex>\pi(i)</tex>.
  
Строка 71: Строка 71:
 
         '''int''' k = 0      <font color=darkgreen>// длина НВП</font>
 
         '''int''' k = 0      <font color=darkgreen>// длина НВП</font>
 
         '''int''' n = <tex>\pi</tex>.size
 
         '''int''' n = <tex>\pi</tex>.size
         '''for''' i = 1 to n
+
         '''for''' i = 1 '''to''' n
 
             x = <tex>\pi</tex>[i]
 
             x = <tex>\pi</tex>[i]
 
             <font color=darkgreen>// в любом случае добавляем в очередь очередной элемент</font>
 
             <font color=darkgreen>// в любом случае добавляем в очередь очередной элемент</font>
Строка 81: Строка 81:
 
                 B.delete(B.next(x))
 
                 B.delete(B.next(x))
 
             '''else'''
 
             '''else'''
                 <font color=darkgreen>// добавленный элемент - максимальный</font>
+
                 <font color=darkgreen>// добавленный элемент максимальный</font>
 
                 <font color=darkgreen>// предыдущие значения не трогаем, очередь увеличилась</font>
 
                 <font color=darkgreen>// предыдущие значения не трогаем, очередь увеличилась</font>
 
                 k = k + 1           
 
                 k = k + 1           
Строка 137: Строка 137:
 
         '''int''' n = <tex>\pi</tex>.size
 
         '''int''' n = <tex>\pi</tex>.size
 
         <font color=blue>'''vector<int>''' predecessor(n)</font> <font color=darkgreen>// резервируем <tex>n</tex> позиций</font>
 
         <font color=blue>'''vector<int>''' predecessor(n)</font> <font color=darkgreen>// резервируем <tex>n</tex> позиций</font>
         '''for''' i = 1 to n
+
         '''for''' i = 1 '''to''' n
 
             x = <tex>\pi</tex>[i]
 
             x = <tex>\pi</tex>[i]
 
             B.insert(x)
 
             B.insert(x)
Строка 155: Строка 155:
 
         '''return''' result</font>
 
         '''return''' result</font>
 
</code>
 
</code>
== Оптимизация до <tex>O(n\operatorname{log}\operatorname{log}k)</tex> ==
+
== Оптимизация до O(n log log k)</tex> ==
 
=== Основная идея ===
 
=== Основная идея ===
 
Чтобы [[Дерево ван Эмде Боаса]] выполняло операции за <tex>O(\operatorname{log}\operatorname{log}k)</tex>, необходимо алфавит обрабатываемых значений уменьшить до <tex>O(k)</tex>.
 
Чтобы [[Дерево ван Эмде Боаса]] выполняло операции за <tex>O(\operatorname{log}\operatorname{log}k)</tex>, необходимо алфавит обрабатываемых значений уменьшить до <tex>O(k)</tex>.
  
Предположим, мы знаем такое {{Acronym|приближение <tex>k</tex>|далее рассмотрим нахождение и насколько оно точное}} <tex>m: m \ge k</tex>. Если мы разобьем всю последовательность на блоки из {{ Acronym|<tex>m</tex> элементов|последний блок может быть меньше}} и нам удастся обрабатывать каждый как перестановку из <tex>m</tex> элементов, то мы получим асимптотическое время <tex>O(n \operatorname{log} \operatorname{log} (k + m))</tex>, а т.к. <tex>m \ge k</tex>, то <tex>O(n \operatorname{log} \operatorname{log} m)</tex>. (Мы будем обрабатывать блоки последовательно, т.е. с предыдущего блока у нас может остаться <tex>k</tex> значений в очереди, которые дополняются <tex>m</tex> значениями очередного блока - получаем верхнее ограничение в <tex>k + m</tex> обрабатываемых возможных значений.)
+
Предположим, мы знаем такое {{Acronym|приближение числа <tex>k</tex>|далее рассмотрим нахождение и насколько оно точное}} числом <tex>m: m \geqslant k</tex>. Если мы разобьем всю последовательность на блоки из {{ Acronym|<tex>m</tex> элементов|последний блок может быть меньше}} и нам удастся обрабатывать каждый как перестановку из <tex>m</tex> элементов, то мы получим асимптотическое время <tex>O(n \operatorname{log} \operatorname{log} (k + m))</tex>, а т.к. <tex>m \geqslant k</tex>, то <tex>O(n \operatorname{log} \operatorname{log} m)</tex>. (Мы будем обрабатывать блоки последовательно, т.е. с предыдущего блока у нас может остаться <tex>k</tex> значений в очереди, которые дополняются <tex>m</tex> значениями очередного блока получаем верхнее ограничение в <tex>k + m</tex> обрабатываемых возможных значений.)
  
 
'''''Описанный здесь алгоритм подбора <tex>m_i</tex> и получение асимптотической оценки <tex>O(n\operatorname{log}\operatorname{log}k)</tex> в других подразделах рассмотрено не будет, т.к. в основном это доказательство, сложного для понимания/реализации ничего нет'''''
 
'''''Описанный здесь алгоритм подбора <tex>m_i</tex> и получение асимптотической оценки <tex>O(n\operatorname{log}\operatorname{log}k)</tex> в других подразделах рассмотрено не будет, т.к. в основном это доказательство, сложного для понимания/реализации ничего нет'''''
  
Рассмотрим последовательность <tex>\{m_0,~m_1,~m_2,~\dots\}</tex>, где <tex> m_{i+1} = m_i ^{\operatorname{log}m_i} = 2^{\operatorname{log}^2m_i}</tex>, <tex>m_0</tex> - некоторое значение, меньшее <tex>k</tex>.
+
Рассмотрим последовательность <tex>\{m_0,~m_1,~m_2,~\dots\}</tex>, где <tex> m_{i+1} = m_i ^{\operatorname{log}m_i} = 2^{\operatorname{log}^2m_i}</tex>, <tex>m_0</tex> некоторое значение, меньшее <tex>k</tex>.
  
Будем последовательно для элементов этой последовательности запускать {{Acronym|алгоритм|о котором ниже}}. Если условие <tex>m \ge k</tex> перестает выполняться, прерываем выполнение. Таким образом, время работы для каждого <tex>m_j</tex> будет <tex>O(n\operatorname{log}\operatorname{log}m_j)</tex>. {{Acronym|Найдется|первый из подобных}} такой <tex>m_i</tex>, который окажется больше <tex>k</tex>, и алгоритм успешно завершится.  
+
Будем последовательно для элементов этой последовательности запускать {{Acronym|алгоритм|о котором ниже}}. Если условие <tex>m \geqslant k</tex> перестает выполняться, прерываем выполнение. Таким образом, время работы для каждого <tex>m_j</tex> будет <tex>O(n\operatorname{log}\operatorname{log}m_j)</tex>. {{Acronym|Найдется|первый из подобных}} такой <tex>m_i</tex>, который окажется больше <tex>k</tex>, и алгоритм успешно завершится.  
  
 
<tex>\operatorname{log}\operatorname{log}m_{i+1} = \operatorname{log}\operatorname{log}2^{\operatorname{log}^2m_i} = \operatorname{log}\operatorname{log}^2m_i = 2\operatorname{log}\operatorname{log}m_i</tex>
 
<tex>\operatorname{log}\operatorname{log}m_{i+1} = \operatorname{log}\operatorname{log}2^{\operatorname{log}^2m_i} = \operatorname{log}\operatorname{log}^2m_i = 2\operatorname{log}\operatorname{log}m_i</tex>
Строка 171: Строка 171:
 
<tex>\operatorname{log}\operatorname{log}m_j = 2^{j-i}\operatorname{log}\operatorname{log}m_i</tex>
 
<tex>\operatorname{log}\operatorname{log}m_j = 2^{j-i}\operatorname{log}\operatorname{log}m_i</tex>
  
{{Acronym|Общее время|для всех обработанных значений m}} работы - <tex>O(n(\sum\limits_{j = 0}\limits^{i}2^{1-j})\operatorname{log}\operatorname{log}m_i) = O(n\operatorname{log}\operatorname{log}m_i)</tex>. Заметим, что <tex>m_i < k^{\operatorname{log}k}</tex>, т.к. в противном случае <tex>m_{i-1} > k</tex>, что противоречит тому, что <tex>m_i</tex> - первый из тех, что больше <tex>k</tex>. Следовательно, <tex>\operatorname{log}\operatorname{log}m_i < 2\operatorname{log}\operatorname{log}k \</tex>.
+
{{Acronym|Общее время|для всех обработанных значений m}} работы <tex>O(n(\sum\limits_{j = 0}\limits^{i}2^{1-j})\operatorname{log}\operatorname{log}m_i) = O(n\operatorname{log}\operatorname{log}m_i)</tex>. Заметим, что <tex>m_i < k^{\operatorname{log}k}</tex>, т.к. в противном случае <tex>m_{i-1} > k</tex>, что противоречит тому, что <tex>m_i</tex> первый из тех, что больше <tex>k</tex>. Следовательно, <tex>\operatorname{log}\operatorname{log}m_i < 2\operatorname{log}\operatorname{log}k \</tex>.
  
Получаем время работы <tex>O(n\operatorname{log}\operatorname{log}k)</tex>
+
Получаем время работы <tex>O(n\operatorname{log}\operatorname{log}k)</tex>.
 
=== Деление на блоки ===
 
=== Деление на блоки ===
 
==== Основная идея ====
 
==== Основная идея ====
 
Разделим исходную перестановку <tex>\pi</tex> на блоки <tex>C_j = \{\pi_{(j-1)m + 1},~\pi_{(j-1)m + 2},~\dots, ~\pi_{(j-1)m + m}\}</tex>.
 
Разделим исходную перестановку <tex>\pi</tex> на блоки <tex>C_j = \{\pi_{(j-1)m + 1},~\pi_{(j-1)m + 2},~\dots, ~\pi_{(j-1)m + m}\}</tex>.
  
Получим сортированные варианты этих блоков <tex>C_j^S</tex>. При лобовой {{Acronym|цифровой|radix}} сортировке мы получим <tex>O(\frac{n}{m}n)</tex>. Дополним каждый элемент <tex>\pi</tex> номером блока, в котором он находится и смещением в этом блоке. Теперь, {{Acronym|рассматривая номер блока как старший разряд, элемент как младший разряд|по смещению внутри блока не сортируем}}, можно сортировать цифровой сортировкой за линейное время <tex>O(n)</tex>.
+
Получим сортированные варианты этих блоков <tex>C_j^S</tex>. Лобовая [[цифровая сортировка]] дает нам время работы <tex>O(\dfrac{n}{m}n)</tex>. Дополним каждый элемент <tex>\pi</tex> номером блока, в котором он находится и смещением в этом блоке. Теперь, {{Acronym|рассматривая номер блока как старший разряд, элемент как младший разряд|по смещению внутри блока не сортируем}}, можно сортировать цифровой сортировкой за линейное время <tex>O(n)</tex>.
  
Перестановка смещений, образованная в сортированном блоке есть не что иное, как обратная перестановка перестановки, элементы которой соотносятся между собой как элементы исходного блока. Находим обратную перестановку к найденной, назовем ее <tex>\xi</tex>
+
Перестановка смещений, образованная в сортированном блоке есть не что иное, как обратная перестановка перестановки, элементы которой соотносятся между собой как элементы исходного блока. Находим обратную перестановку к найденной, назовем ее <tex>\xi</tex>.
 
==== Пример ====
 
==== Пример ====
 
Пусть <tex>m = 5</tex>. Исходно:
 
Пусть <tex>m = 5</tex>. Исходно:
Строка 209: Строка 209:
 
=== Обработка блока ===
 
=== Обработка блока ===
 
==== Основная идея ====
 
==== Основная идея ====
* {{Acronym|Достаем из очереди ключи|если это первый блок - ничего не делаем}} и конвертируем их в элементы <tex>\pi</tex>.
+
* {{Acronym|Достаем из очереди ключи|если это первый блок ничего не делаем}} и конвертируем их в элементы <tex>\pi</tex>.
  
 
* Классический [[Сортировка слиянием#Принцип работы#Слияние двух массивов | merge]] только что полученных элементов <tex>\pi</tex> с элементами нового блока, но с модификацией: генерируются 2 дополнительных массива - индексы элементов исходных массивов в новом. Слияние массивов назовем <tex>merged</tex>.
 
* Классический [[Сортировка слиянием#Принцип работы#Слияние двух массивов | merge]] только что полученных элементов <tex>\pi</tex> с элементами нового блока, но с модификацией: генерируются 2 дополнительных массива - индексы элементов исходных массивов в новом. Слияние массивов назовем <tex>merged</tex>.
Строка 215: Строка 215:
 
* На массив индексов, относящиеся к новому блоку, действует перестановка смещений сортированного варианта этого блока. Таким образом мы добиваемся эквиваленции отношений {{Acronym|ключей|смещений}} к отношениям элементов в очереди и элементов в блоке, а ключи находятся в диапазоне <tex>O(m)</tex>.  
 
* На массив индексов, относящиеся к новому блоку, действует перестановка смещений сортированного варианта этого блока. Таким образом мы добиваемся эквиваленции отношений {{Acronym|ключей|смещений}} к отношениям элементов в очереди и элементов в блоке, а ключи находятся в диапазоне <tex>O(m)</tex>.  
  
* Первый массив индексов, который соответствует элементам, ранее находящимся в очереди, вновь кладутся в очередь (обычными <tex>insert</tex>-ами). Второй массив обрабатывается описанным выше алгоритмом <tex>LIS</tex>, при том массив <tex>predecessor</tex> строится из ключей с помощью <tex>merged</tex>
+
* Первый массив индексов, который соответствует элементам, ранее находящимся в очереди, вновь кладутся в очередь (обычными <tex>insert</tex>-ами). Второй массив обрабатывается описанным выше алгоритмом <tex>LIS</tex>, при том массив <tex>predecessor</tex> строится из ключей с помощью <tex>merged</tex>.
  
 
==== Визуализация ====
 
==== Визуализация ====
Помеченные <font color=green>зеленым</font> - условные данные. Остальное - условные операции. Например <tex>keys \longrightarrow merged</tex> значит взять элементы из массива <tex>merged</tex> c индексами из <tex>keys</tex>. <tex>merge \longrightarrow merged</tex> - здесь <tex>merged</tex> обозначает результат операции '''merge'''.
+
Помеченные <font color=green>зеленым</font> условные данные. Остальное условные операции. Например <tex>keys \longrightarrow merged</tex> значит взять элементы из массива <tex>merged</tex> c индексами из <tex>keys</tex>. <tex>merge \longrightarrow merged</tex> здесь <tex>merged</tex> обозначает результат операции '''merge'''.
  
 
[[Файл:blockHandle.jpg]]
 
[[Файл:blockHandle.jpg]]
Строка 274: Строка 274:
 
<tex>merged: \{3, 4, 8, 9, 10\}</tex>
 
<tex>merged: \{3, 4, 8, 9, 10\}</tex>
 
===== Второй блок =====
 
===== Второй блок =====
Восстанавливаем элементы <tex>B: \{1, 2, 3\}</tex> из <tex>merged: \{3, 4, 8, 9, 10\}</tex>: <tex>\{3, 4, 8\}</tex>
+
Восстанавливаем элементы <tex>B: \{1, 2, 3\}</tex> из <tex>merged: \{3, 4, 8, 9, 10\}</tex>: <tex>\{3, 4, 8\}</tex>.
 
{|
 
{|
 
| ||
 
| ||
Строка 305: Строка 305:
 
{| class="wikitable" style="center"
 
{| class="wikitable" style="center"
 
|-align="center"
 
|-align="center"
| colspan="3"|<tex>ind's\#0</tex> - индексы текущих
+
| colspan="3"|<tex>ind's\#0</tex> индексы текущих
 
|-align="center"
 
|-align="center"
 
| 3||4||7
 
| 3||4||7
Строка 312: Строка 312:
 
{| class="wikitable" style="center"
 
{| class="wikitable" style="center"
 
|-align="center"
 
|-align="center"
| colspan="5"|<tex>ind's\#1</tex> - индексы новых
+
| colspan="5"|<tex>ind's\#1</tex> индексы новых
 
|-align="center"
 
|-align="center"
 
| 1||2||5||6||8
 
| 1||2||5||6||8
Строка 363: Строка 363:
  
 
===== Третий блок =====
 
===== Третий блок =====
Восстанавливаем элементы <tex>B: \{1, 2, 5, 8\}</tex> из <tex>merged: \{1,2,3,4,5,6,8,12\}</tex>: <tex>\{1, 2, 5, 12\}</tex>
+
Восстанавливаем элементы <tex>B: \{1, 2, 5, 8\}</tex> из <tex>merged: \{1,2,3,4,5,6,8,12\}</tex>: <tex>\{1, 2, 5, 12\}</tex>.
 
{|
 
{|
 
| ||
 
| ||
Строка 394: Строка 394:
 
{| class="wikitable" style="center"
 
{| class="wikitable" style="center"
 
|-align="center"
 
|-align="center"
| colspan="4"|<tex>ind's\#0</tex> - индексы текущих
+
| colspan="4"|<tex>ind's\#0</tex> индексы текущих
 
|-align="center"
 
|-align="center"
 
| 1||2||3||6
 
| 1||2||3||6
Строка 401: Строка 401:
 
{| class="wikitable" style="center"
 
{| class="wikitable" style="center"
 
|-align="center"
 
|-align="center"
| colspan="2"|<tex>ind's\#1</tex> - индексы новых
+
| colspan="2"|<tex>ind's\#1</tex> индексы новых
 
|-align="center"
 
|-align="center"
 
| 4||5
 
| 4||5
Строка 463: Строка 463:
 
| 1||2||5||7||11
 
| 1||2||5||7||11
 
|}
 
|}
 +
== См. также ==
 +
*[[Дерево ван Эмде Боаса]]
 +
*[[Задача о наибольшей возрастающей подпоследовательности]]
 +
*[[Задача о наибольшей общей подпоследовательности]]
 +
*[[Наибольшая общая возрастающая подпоследовательность]]
 +
*[[Задача о наибольшей общей палиндромной подпоследовательности]]
 +
 +
== Источники информации ==
 +
* [http://www.bcs.org/upload/pdf/ewic_vs08_s2paper2.pdf] (07.01.2017)
 +
 +
[[Категория: Дискретная математика и алгоритмы]]
 +
[[Категория: Динамическое программирование]]

Версия 22:25, 7 января 2017

Эта статья находится в разработке!


Задача:
Дана перестановка [math]\pi[/math] [math]\{1, 2,~\dots,~n\}[/math]. Требуется найти НВП [math]\pi[/math] за [math]O(n\operatorname{log}\operatorname{log}k)[/math], где [math]k[/math] — длина НВП.


Task.jpg

Алгоритм O(n log log n)

Нахождение длины НВП

Основная идея

Пусть [math]\pi(n)[/math] — входная перестановка.

Для каждой длины [math]l = 1, 2, \dots[/math] предполагаемой НВП находим наименьший элемент, что может быть последним в возрастающей подпоследовательности длины [math]l[/math], запишем их в массив [math]B[l][/math].

Если обрабатываемый элемент [math]\pi(i)[/math] больше последнего элемента какой-нибудь возрастающей последовательности, он может ее увеличить.

Будем последовательно обрабатывать элементы [math]\pi(1), \pi(2),~\dots,~\pi(n)[/math]:

  • Если [math]\pi(i)[/math] больше [math]\pi(1), \pi(2),~\dots~,\pi(i-1)[/math] , значит с ним можно сделать максимальную, из уже рассмотренных, возрастающую подпоследовательность. Записываем его в конец [math]B[/math].
  • Иначе [math]\pi(i)[/math] заменяет наименьший лучший элемент, из тех, что больше [math]\pi(i)[/math].

Следует отметить, что полученный массив также образует возрастающую последовательность, где мы должны выполнять операции [math]insert, next, delete[/math], соответственно целесообразно использовать приоритетную очередь, реализованную через Дерево ван Эмде Боаса. Таким образом получаем [math]O(\operatorname{log}\operatorname{log} n)[/math] амортизированного времени на одну операцию.

Пример

Типы операций:

Operation1.jpg

Operation2 1.jpg [math]\longrightarrow[/math] Operation2 2.jpg

Последовательность:

[math]\pi_1[/math] [math]\pi_2[/math] [math]\pi_3[/math] [math]\pi_4[/math] [math]\pi_5[/math] [math]\pi_6[/math] [math]\pi_7[/math] [math]\pi_8[/math] [math]\pi_9[/math] [math]\pi_{10}[/math] [math]\pi_{11}[/math] [math]\pi_{12}[/math]
9 3 10 4 8 1 2 12 6 5 7 11

Состояние очереди при каждом добавлении:

[math]B_1[/math] [math]B_2[/math] [math]B_3[/math] [math]B_4[/math] [math]B_5[/math] [math]~\pi_i~[/math]
9 9
3 3
3 10 10
3 4 4
3 4 8 8
1 4 8 1
1 2 8 2
1 2 8 12 12
1 2 6 12 6
1 2 5 12 5
1 2 5 7 7
1 2 5 7 11 11

Псевдокод

   int LIS(vector<int> [math]\pi[/math])
       PriorityQueue B // рабочая приоритетная очередь
       int k = 0       // длина НВП
       int n = [math]\pi[/math].size
       for i = 1 to n
           x = [math]\pi[/math][i]
           // в любом случае добавляем в очередь очередной элемент
           // устаревшие будем удалять
           B.insert(x)
           if [math]\exists[/math] B.next(x)
               // добавленный элемент — не максимальный
               // удаляем предыдущее значение — заменяем следующий
               B.delete(B.next(x))
           else
               // добавленный элемент — максимальный
               // предыдущие значения не трогаем, очередь увеличилась
               k = k + 1           
       return k

Расширение алгоритма до нахождения НВП

Основная идея

Будем запоминать пары: для каждого элемента записываем его "предшественника".

Тогда, выбрав какой-нибудь лучший элемент для максимальной длины, мы можем легко восстановить НВП .

Общий вид алгоритма

[math]B_1[/math] [math]B_2[/math] [math]B_3[/math] [math]B_4[/math] [math]B_5[/math] [math]~\pi_i~[/math]
9 9
3 3
3 10 10
3 4 4
3 4 8 8
1 4 8 1
1 2 8 2
1 2 8 12 12
1 2 6 12 6
1 2 5 12 5
1 2 5 7 7
1 2 5 7 11 11
predecessor
1 2 3 4 5 6 7 8 9 10 11 12
1 3 2 2 5 4 3 7 8

Псевдокод

   vector<int> LIS(vector<int> [math]\pi[/math])
       PriorityQueue B
       int k = 0
       int n = [math]\pi[/math].size
       vector<int> predecessor(n) // резервируем [math]n[/math] позиций
       for i = 1 to n
           x = [math]\pi[/math][i]
           B.insert(x)
           predecessor[x] = B.prev(x)
           if [math]\exists[/math] B.next(x)
               B.delete(B.next(x))
           else
               k = k + 1
       // по цепочке от последнего элемента 
       // восстанавливаем НВП
       vector<int> result
       int cur = B.max()
       result += [cur]
       while [math]\exists[/math] predecessor[cur] 
           result += [predecessor[cur]]
           cur = predecessor[cur]
       return result

Оптимизация до O(n log log k)</tex>

Основная идея

Чтобы Дерево ван Эмде Боаса выполняло операции за [math]O(\operatorname{log}\operatorname{log}k)[/math], необходимо алфавит обрабатываемых значений уменьшить до [math]O(k)[/math].

Предположим, мы знаем такое приближение числа [math]k[/math] числом [math]m: m \geqslant k[/math]. Если мы разобьем всю последовательность на блоки из [math]m[/math] элементов и нам удастся обрабатывать каждый как перестановку из [math]m[/math] элементов, то мы получим асимптотическое время [math]O(n \operatorname{log} \operatorname{log} (k + m))[/math], а т.к. [math]m \geqslant k[/math], то [math]O(n \operatorname{log} \operatorname{log} m)[/math]. (Мы будем обрабатывать блоки последовательно, т.е. с предыдущего блока у нас может остаться [math]k[/math] значений в очереди, которые дополняются [math]m[/math] значениями очередного блока — получаем верхнее ограничение в [math]k + m[/math] обрабатываемых возможных значений.)

Описанный здесь алгоритм подбора [math]m_i[/math] и получение асимптотической оценки [math]O(n\operatorname{log}\operatorname{log}k)[/math] в других подразделах рассмотрено не будет, т.к. в основном это доказательство, сложного для понимания/реализации ничего нет

Рассмотрим последовательность [math]\{m_0,~m_1,~m_2,~\dots\}[/math], где [math] m_{i+1} = m_i ^{\operatorname{log}m_i} = 2^{\operatorname{log}^2m_i}[/math], [math]m_0[/math] — некоторое значение, меньшее [math]k[/math].

Будем последовательно для элементов этой последовательности запускать алгоритм. Если условие [math]m \geqslant k[/math] перестает выполняться, прерываем выполнение. Таким образом, время работы для каждого [math]m_j[/math] будет [math]O(n\operatorname{log}\operatorname{log}m_j)[/math]. Найдется такой [math]m_i[/math], который окажется больше [math]k[/math], и алгоритм успешно завершится.

[math]\operatorname{log}\operatorname{log}m_{i+1} = \operatorname{log}\operatorname{log}2^{\operatorname{log}^2m_i} = \operatorname{log}\operatorname{log}^2m_i = 2\operatorname{log}\operatorname{log}m_i[/math]

[math]\operatorname{log}\operatorname{log}m_j = 2^{j-i}\operatorname{log}\operatorname{log}m_i[/math]

Общее время работы — [math]O(n(\sum\limits_{j = 0}\limits^{i}2^{1-j})\operatorname{log}\operatorname{log}m_i) = O(n\operatorname{log}\operatorname{log}m_i)[/math]. Заметим, что [math]m_i \lt k^{\operatorname{log}k}[/math], т.к. в противном случае [math]m_{i-1} \gt k[/math], что противоречит тому, что [math]m_i[/math] — первый из тех, что больше [math]k[/math]. Следовательно, [math]\operatorname{log}\operatorname{log}m_i \lt 2\operatorname{log}\operatorname{log}k \[/math].

Получаем время работы [math]O(n\operatorname{log}\operatorname{log}k)[/math].

Деление на блоки

Основная идея

Разделим исходную перестановку [math]\pi[/math] на блоки [math]C_j = \{\pi_{(j-1)m + 1},~\pi_{(j-1)m + 2},~\dots, ~\pi_{(j-1)m + m}\}[/math].

Получим сортированные варианты этих блоков [math]C_j^S[/math]. Лобовая цифровая сортировка дает нам время работы [math]O(\dfrac{n}{m}n)[/math]. Дополним каждый элемент [math]\pi[/math] номером блока, в котором он находится и смещением в этом блоке. Теперь, рассматривая номер блока как старший разряд, элемент как младший разряд, можно сортировать цифровой сортировкой за линейное время [math]O(n)[/math].

Перестановка смещений, образованная в сортированном блоке есть не что иное, как обратная перестановка перестановки, элементы которой соотносятся между собой как элементы исходного блока. Находим обратную перестановку к найденной, назовем ее [math]\xi[/math].

Пример

Пусть [math]m = 5[/math]. Исходно:

Блок 1 1 1 1 1 2 2 2 2 2 3 3
[math]\pi[/math] 9 3 10 4 8 1 2 12 6 5 7 11
Смещение 1 2 3 4 5 1 2 3 4 5 1 2

После сортировки:

Блок 1 1 1 1 1 2 2 2 2 2 3 3
[math]\pi[/math] 3 4 8 9 10 1 2 5 6 12 7 11
Смещение 2 4 5 1 3 1 2 5 4 3 1 2

Обратные перестановки ([math]\xi[/math]):

1 2 3
4 1 5 2 3 1 2 5 4 3 1 2

Обработка блока

Основная идея

  • Достаем из очереди ключи и конвертируем их в элементы [math]\pi[/math].
  • Классический merge только что полученных элементов [math]\pi[/math] с элементами нового блока, но с модификацией: генерируются 2 дополнительных массива - индексы элементов исходных массивов в новом. Слияние массивов назовем [math]merged[/math].
  • На массив индексов, относящиеся к новому блоку, действует перестановка смещений сортированного варианта этого блока. Таким образом мы добиваемся эквиваленции отношений ключей к отношениям элементов в очереди и элементов в блоке, а ключи находятся в диапазоне [math]O(m)[/math].
  • Первый массив индексов, который соответствует элементам, ранее находящимся в очереди, вновь кладутся в очередь (обычными [math]insert[/math]-ами). Второй массив обрабатывается описанным выше алгоритмом [math]LIS[/math], при том массив [math]predecessor[/math] строится из ключей с помощью [math]merged[/math].

Визуализация

Помеченные зеленым — условные данные. Остальное — условные операции. Например [math]keys \longrightarrow merged[/math] значит взять элементы из массива [math]merged[/math] c индексами из [math]keys[/math]. [math]merge \longrightarrow merged[/math] — здесь [math]merged[/math] обозначает результат операции merge.

BlockHandle.jpg

Для первого блока содержательным является лишь ветка, начинающаяся с [math]C_j^S \longrightarrow merge \longrightarrow ind's\#1[/math], что не противоречит представленной схеме.

Пример

Первый блок
Первый блок
9 3 10 4 8
1 2 3 4 5
Cортированный
3 4 8 9 10
2 4 5 1 3

[math]merged[/math] аналогичен сортированному, т.к. предыдущих ключей нет.

Ключи сортированного блока
2 4 5 1 3
[math]\xi[/math]
4 1 5 2 3

Пропускаем их через [math]LIS[/math]:

4 4
1 1
1 5 5
1 2 2
1 2 3 3

Результат работы

[math]B: \{1, 2, 3\}[/math]

[math]merged: \{3, 4, 8, 9, 10\}[/math]

Второй блок

Восстанавливаем элементы [math]B: \{1, 2, 3\}[/math] из [math]merged: \{3, 4, 8, 9, 10\}[/math]: [math]\{3, 4, 8\}[/math].

Второй блок
1 2 12 6 5
1 2 3 4 5
Cортированный
1 2 5 6 12
1 2 5 4 3
[math]merged[/math]
1 2 3 4 5 6 8 12
[math]ind's\#0[/math] — индексы текущих
3 4 7
[math]ind's\#1[/math] — индексы новых
1 2 5 6 8
Ключи сортированного блока
1 2 5 4 3
[math]\xi[/math]
1 2 5 4 3

Восстанавливаем порядок новых из [math]ind's\#1[/math] и [math]\xi[/math]:

новые ключи
1 2 8 6 5

Обновление старых ключей:

3 3
3 4 4
3 4 7 7

[math]LIS[/math] новых:

1 4 7 1
1 2 7 2
1 2 7 8 8
1 2 6 8 6
1 2 5 8 5

Результат работы

[math]B: \{1, 2, 5, 8\}[/math]

[math]merged: \{1,2,3,4,5,6,8,12\}[/math]

Третий блок

Восстанавливаем элементы [math]B: \{1, 2, 5, 8\}[/math] из [math]merged: \{1,2,3,4,5,6,8,12\}[/math]: [math]\{1, 2, 5, 12\}[/math].

Первый блок
7 11
1 2
Cортированный
7 11
1 2
[math]merged[/math]
1 2 5 7 11 12
[math]ind's\#0[/math] — индексы текущих
1 2 3 6
[math]ind's\#1[/math] — индексы новых
4 5
Ключи сортированного блока
1 2
[math]\xi[/math]
1 2

Восстанавливаем порядок новых из [math]ind's\#1[/math] и [math]\xi[/math]:

новые ключи
4 5

Обновление старых ключей:

1 4 7 1
1 2 7 2
1 2 3 3
1 2 3 6 6

[math]LIS[/math] новых:

1 2 3 4 4
1 2 3 4 5 5

Результат работы

[math]B: \{1, 2, 3, 4, 5\}[/math]

[math]merged: \{1,2,5,7,11,12\}[/math]

Восстановление НВП
[math]predecessor[/math]
1 2 3 4 5 6 7 8 9 10 11 12
1 3 2 2 5 4 3 7 8

Начинаем восстановление с [math]merged[5] = 11[/math]:

обратный порядок
11 7 5 2 1
НВП
1 2 5 7 11

См. также

Источники информации

  • [1] (07.01.2017)