Быстрый поиск наибольшей возрастающей подпоследовательности — различия между версиями
м (→Второй блок: Немного структурирован) |
м (rollbackEdits.php mass rollback) |
||
| (не показано 10 промежуточных версий 4 участников) | |||
| Строка 1: | Строка 1: | ||
{{Задача | {{Задача | ||
| − | |definition = Дана | + | |definition = Дана перестановка <tex>\pi</tex> множества <tex>~\{1, 2,~\dots,~n\}</tex>. Требуется найти [[Задача о наибольшей возрастающей подпоследовательности | НВП]] <tex>\pi</tex> за <tex>O(n\operatorname{log}\operatorname{log}k)</tex>, где <tex>k</tex> — длина НВП. |
}} | }} | ||
[[Файл:Task.jpg]] | [[Файл:Task.jpg]] | ||
| − | == Алгоритм O(n log log n) == | + | == Алгоритм за O(n log log n) == |
=== Нахождение длины НВП === | === Нахождение длины НВП === | ||
==== Основная идея ==== | ==== Основная идея ==== | ||
| − | Пусть <tex>\ | + | Пусть <tex>\{\pi_1,\pi_2,~\dots,~\pi_n\}</tex> — входная перестановка. |
| − | + | Будем последовательно обрабатывать элементы в порядке <tex>\pi_1, \pi_2,~\dots,~\pi_n\colon</tex> | |
| − | + | Для каждой длины <tex>l = 1, 2,~\dots,~n</tex> предполагаемой НВП находим наименьший элемент, который может быть последним в возрастающей подпоследовательности длины <tex>l</tex> и запишем его в массив <tex>B_l</tex>. Будем называть его наилучшим элементом для длины <tex>l</tex>. | |
| − | + | * Если <tex>\pi_i</tex> больше каждого элемента <tex>B</tex>, вычисленного для подпоследовательности <tex>\pi_1, \pi_2,~\dots~,\pi_{i-1}</tex>, тогда с ним можно сделать возрастающую подпоследовательность максимальной длины из уже рассмотренных, в которой он будет последним элементом. Значит, записываем его в конец <tex>B</tex>. | |
| + | * Иначе <tex>\pi_i</tex> будет наилучшим элементом для уже существующей длины и сможет улучшить только один элемент в <tex>B</tex>, тогда мы находим наименьшее <tex>k\colon B_k > \pi_i</tex> и заменяем <tex>B_k</tex> элементом <tex>\pi_i</tex>. | ||
| − | + | Следует отметить, что полученный массив также образует возрастающую последовательность, на котором мы должны выполнять операции <tex>\mathrm{insert}, \mathrm{next}, \mathrm{delete}</tex>, соответственно целесообразно использовать [[ Приоритетные очереди | приоритетную очередь]], реализованную через [[Дерево ван Эмде Боаса]]. Так как данная структура данных производит описанные операции за <tex>O(\operatorname{log} k)</tex>, где k — количество бит чисел, которые позволяет хранить дерево, то полученный алгоритм работает за <tex>O(n\operatorname{log}\operatorname{log} n)</tex>, потому что все элементы последовательности не превосходят n. | |
| − | |||
| − | |||
| − | Следует отметить, что полученный массив также образует возрастающую последовательность, | ||
==== Пример ==== | ==== Пример ==== | ||
| − | + | ''' Типы операций ''' | |
* Добавление элемента, который больше всех предыдущих: | * Добавление элемента, который больше всех предыдущих: | ||
| Строка 31: | Строка 29: | ||
[[Файл:Operation2_1.jpg]] <tex>\longrightarrow</tex> [[Файл:Operation2_2.jpg]] | [[Файл:Operation2_1.jpg]] <tex>\longrightarrow</tex> [[Файл:Operation2_2.jpg]] | ||
| − | + | ''' Пример последовательности ''' | |
{| class="wikitable" align="leftborder" style="color: black; background-color:#ffffcc;" cellpadding="10" | {| class="wikitable" align="leftborder" style="color: black; background-color:#ffffcc;" cellpadding="10" | ||
|-align="center" | |-align="center" | ||
! <tex>\pi_1</tex> || <tex>\pi_2</tex> || <tex>\pi_3</tex> || <tex>\pi_4</tex> || <tex>\pi_5</tex> || <tex>\pi_6</tex> || <tex>\pi_7</tex> || <tex>\pi_8</tex> || <tex>\pi_9</tex> || <tex>\pi_{10}</tex> || <tex>\pi_{11}</tex> || <tex>\pi_{12}</tex> | ! <tex>\pi_1</tex> || <tex>\pi_2</tex> || <tex>\pi_3</tex> || <tex>\pi_4</tex> || <tex>\pi_5</tex> || <tex>\pi_6</tex> || <tex>\pi_7</tex> || <tex>\pi_8</tex> || <tex>\pi_9</tex> || <tex>\pi_{10}</tex> || <tex>\pi_{11}</tex> || <tex>\pi_{12}</tex> | ||
|-align="center" | |-align="center" | ||
| − | | 9 || 3 || 10 || 4 || 8 || 1 || 2 || 12 || 6 || 5 || 7 || 11 | + | | <tex>9</tex> || <tex>3</tex> || <tex>10</tex> || <tex>4</tex> || <tex>8</tex> || <tex>1</tex> || <tex>2</tex> || <tex>12</tex> || <tex>6</tex> || <tex>5</tex> || <tex>7</tex> || <tex>11</tex> |
|} | |} | ||
| − | + | ||
| + | ''' Состояние очереди при каждом добавлении ''' | ||
{| class="wikitable" align="leftborder" style="color: black; background-color:#ffffcc;" cellpadding="10" | {| class="wikitable" align="leftborder" style="color: black; background-color:#ffffcc;" cellpadding="10" | ||
|-align="center" | |-align="center" | ||
! <tex>B_1</tex> || <tex>B_2</tex> || <tex>B_3</tex> || <tex>B_4</tex> || <tex>B_5</tex> || <tex>~\pi_i~</tex> | ! <tex>B_1</tex> || <tex>B_2</tex> || <tex>B_3</tex> || <tex>B_4</tex> || <tex>B_5</tex> || <tex>~\pi_i~</tex> | ||
|-align="center" | |-align="center" | ||
| − | | style="background:# | + | | style="background:#FFCFCF"| <tex>9</tex> || || || || || style="background: #CFCFFF"| <tex>9</tex> |
|-align="center" | |-align="center" | ||
| − | | style="background:# | + | | style="background:#FFCFCF"| <tex>3</tex> || || || || || style="background: #CFCFFF"| <tex>3</tex> |
|-align="center" | |-align="center" | ||
| − | | 3 || style="background:# | + | | <tex>3</tex> || style="background:#FFCFCF"| <tex>10</tex> || || || || style="background: #CFCFFF"| <tex>10</tex> |
|-align="center" | |-align="center" | ||
| − | | 3 || style="background:# | + | | <tex>3</tex> || style="background:#FFCFCF"| <tex>4</tex> || || || || style="background: #CFCFFF"| <tex>4</tex> |
|-align="center" | |-align="center" | ||
| − | | 3 || 4 || style="background:# | + | | <tex>3</tex> || <tex>4</tex> || style="background:#FFCFCF"| <tex>8</tex> || || || style="background: #CFCFFF"| <tex>8</tex> |
|-align="center" | |-align="center" | ||
| − | | style="background:# | + | | style="background:#FFCFCF"| <tex>1</tex> || <tex>4</tex> || <tex>8</tex> || || || style="background: #CFCFFF"| <tex>1</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || style="background:# | + | | <tex>1</tex> || style="background:#FFCFCF"| <tex>2</tex> || <tex>8</tex> || || || style="background: #CFCFFF"| <tex>2</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || 8 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || <tex>8</tex> || style="background:#FFCFCF"| <tex>12</tex> || || style="background: #CFCFFF"| <tex>12</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || style="background:#FFCFCF"| <tex>6</tex> || <tex>12</tex> || || style="background: #CFCFFF"| <tex>6</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || style="background:#FFCFCF"| <tex>5</tex> || <tex>12</tex> || || style="background: #CFCFFF"| <tex>5</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || 5 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || <tex>5</tex> || style="background:#FFCFCF"| <tex>7</tex> || || style="background: #CFCFFF"| <tex>7</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || 5 || 7 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || <tex>5</tex> || <tex>7</tex> || style="background:#FFCFCF"| <tex>11</tex> || style="background: #CFCFFF"| <tex>11</tex> |
|} | |} | ||
==== Псевдокод ==== | ==== Псевдокод ==== | ||
| − | + | ||
| − | + | '''int''' LIS(<tex>\pi</tex>[n]) | |
| − | + | '''PriorityQueue''' B <font color=darkgreen>// рабочая приоритетная очередь</font> | |
| − | + | '''int''' k = 0 <font color=darkgreen>// длина НВП</font> | |
| − | + | '''for''' i = 1 '''to''' n | |
| − | + | x = <tex>\pi</tex>[i] | |
| − | + | <font color=darkgreen>// в любом случае добавляем в очередь очередной элемент</font> | |
| − | + | <font color=darkgreen>// устаревшие будем удалять</font> | |
| − | + | B.insert(x) | |
| − | + | '''if''' <tex>\exists</tex> B.next(x) | |
| − | + | <font color=darkgreen>// добавленный элемент — не максимальный</font> | |
| − | + | <font color=darkgreen>// удаляем следующее за x значение </font> | |
| − | + | B.delete(B.next(x)) | |
| − | + | '''else''' | |
| − | + | <font color=darkgreen>// добавленный элемент — максимальный</font> | |
| − | + | <font color=darkgreen>// предыдущие значения не трогаем, очередь увеличилась</font> | |
| − | + | k = k + 1 | |
| − | + | '''return''' k | |
=== Расширение алгоритма до нахождения НВП === | === Расширение алгоритма до нахождения НВП === | ||
| Строка 92: | Строка 91: | ||
Будем запоминать пары: для каждого элемента записываем его "предшественника". | Будем запоминать пары: для каждого элемента записываем его "предшественника". | ||
| − | Тогда, | + | Тогда, пройдя по предшественникам, начиная с последнего элемента очереди <tex>B</tex>, мы можем восстановить НВП. |
==== Общий вид алгоритма ==== | ==== Общий вид алгоритма ==== | ||
{| class="wikitable" align="leftborder" style="color: black; background-color:#ffffcc;" cellpadding="10" | {| class="wikitable" align="leftborder" style="color: black; background-color:#ffffcc;" cellpadding="10" | ||
| Строка 98: | Строка 97: | ||
! <tex>B_1</tex> || <tex>B_2</tex> || <tex>B_3</tex> || <tex>B_4</tex> || <tex>B_5</tex> || <tex>~\pi_i~</tex> | ! <tex>B_1</tex> || <tex>B_2</tex> || <tex>B_3</tex> || <tex>B_4</tex> || <tex>B_5</tex> || <tex>~\pi_i~</tex> | ||
|-align="center" | |-align="center" | ||
| − | | 9 || || || || || style="background: # | + | | <tex>9</tex> || || || || || style="background: #CFCFFF"| <tex>9</tex> |
|-align="center" | |-align="center" | ||
| − | | 3 || || || || || style="background: # | + | | <tex>3</tex> || || || || || style="background: #CFCFFF"| <tex>3</tex> |
|-align="center" | |-align="center" | ||
| − | | 3 || 10 || || || || style="background: # | + | | <tex>3</tex> || <tex>10</tex> || || || || style="background: #CFCFFF"| <tex>10</tex> |
|-align="center" | |-align="center" | ||
| − | | 3 || 4 || || || || style="background: # | + | | <tex>3</tex> || <tex>4</tex> || || || || style="background: #CFCFFF"| <tex>4</tex> |
|-align="center" | |-align="center" | ||
| − | | 3 || 4 || 8 || || || style="background: # | + | | <tex>3</tex> || <tex>4</tex> || <tex>8</tex> || || || style="background: #CFCFFF"| <tex>8</tex> |
|-align="center" | |-align="center" | ||
| − | | style="background:# | + | | style="background:#FFDF90"| <tex>1</tex> || <tex>4</tex> || <tex>8</tex> || || || style="background: #CFCFFF"| <tex>1</tex> |
|-align="center" | |-align="center" | ||
| − | | style="background:# | + | | style="background:#FFCFCF"| <tex>1</tex> || style="background:#FFDF90"| <tex>2</tex> || <tex>8</tex> || || || style="background: #CFCFFF"| <tex>2</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || style="background:# | + | | <tex>1</tex> || style="background:#FFDF90"| <tex>2</tex> || <tex>8</tex> || <tex>12</tex> || || style="background: #CFCFFF"| <tex>12</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || style="background:# | + | | <tex>1</tex> || style="background:#FFDF90"| <tex>2</tex> || <tex>6</tex> || <tex>12</tex> || || style="background: #CFCFFF"| <tex>6</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || style="background:# | + | | <tex>1</tex> || style="background:#FFCFCF"| <tex>2</tex> || style="background:#FFDF90"| <tex>5</tex> || <tex>12</tex> || || style="background: #CFCFFF"| <tex>5</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || style="background:#FFCFCF"| <tex>5</tex> || style="background:#FFDF90"| <tex>7</tex> || || style="background: #CFCFFF"| <tex>7</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || 5 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || <tex>5</tex> || style="background:#FFCFCF"| <tex>7</tex> || style="background:#FFCFCF"| <tex>11</tex> || style="background: #CFCFFF"| <tex>11</tex> |
| − | |||
|} | |} | ||
| − | {| class="wikitable | + | {| class="wikitable" style="color: black; background-color:#ffffcc;" cellpadding="10" |
|-align="center" | |-align="center" | ||
! colspan="12" | predecessor | ! colspan="12" | predecessor | ||
|-align="center" | |-align="center" | ||
| − | ! 1 || 2 || 3 || 4 || 5 || 6 || 7 || 8 || 9 || 10 || 11 || 12 | + | ! <tex>1</tex> || <tex>2</tex> || <tex>3</tex> || <tex>4</tex> || <tex>5</tex> || <tex>6</tex> || <tex>7</tex> || <tex>8</tex> || <tex>9</tex> || <tex>10</tex> || <tex>11</tex> || <tex>12</tex> |
|-align="center" | |-align="center" | ||
| − | | || 1 || || 3 || 2 || 2 || 5 || 4 || || 3 || 7 || 8 | + | | || <tex>1</tex> || || <tex>3</tex> || <tex>2</tex> || <tex>2</tex> || <tex>5</tex> || <tex>4</tex> || || <tex>3</tex> || <tex>7</tex> || <tex>8</tex> |
|} | |} | ||
| + | |||
==== Псевдокод ==== | ==== Псевдокод ==== | ||
| − | + | ||
| − | + | '''int[]''' LIS(<tex>\pi</tex>[n]) | |
| − | + | '''PriorityQueue''' B | |
| − | + | '''int''' k = 0 | |
| − | + | <font color=blue>'''int''' predecessor[n]</font> <font color=darkgreen>// резервируем <tex>n</tex> позиций</font> | |
| − | + | '''for''' i = 1 '''to''' n | |
| − | + | x = <tex>\pi</tex>[i] | |
| − | + | B.insert(x) | |
| − | + | <font color=blue>predecessor[x] = B.prev(x)</font> | |
| − | + | '''if''' <tex>\exists</tex> B.next(x) | |
| − | + | B.delete(B.next(x)) | |
| − | + | '''else''' | |
| − | + | k = k + 1 | |
| − | + | <font color=darkgreen>// по цепочке от последнего элемента | |
| − | + | // восстанавливаем НВП</font> | |
| − | + | <font color=blue>'''int''' result[k] | |
| − | + | '''int''' cur = B.max | |
| − | + | '''for''' i = k - 1 '''downto''' 0 | |
| − | + | result[i] = cur | |
| − | + | cur = predecessor[cur] | |
| − | + | '''return''' result</font> | |
| − | |||
== Оптимизация до O(n log log k) == | == Оптимизация до O(n log log k) == | ||
| − | === Основная идея === | + | ===Основная идея=== |
Чтобы [[Дерево ван Эмде Боаса]] выполняло операции за <tex>O(\operatorname{log}\operatorname{log}k)</tex>, необходимо алфавит обрабатываемых значений уменьшить до <tex>O(k)</tex>. | Чтобы [[Дерево ван Эмде Боаса]] выполняло операции за <tex>O(\operatorname{log}\operatorname{log}k)</tex>, необходимо алфавит обрабатываемых значений уменьшить до <tex>O(k)</tex>. | ||
| − | Предположим, мы знаем такое | + | Предположим, мы знаем такое приближение числа <tex>k</tex> числом <tex>m: m \geqslant k</tex>. Мы обсудим, как найти такое <tex>m</tex> позже. |
| − | + | Во время обработки ключей элементов описанный выше алгоритм <tex>\mathrm{LIS}</tex> работает только с очередью <tex>B</tex> и не зависит от предыдущих элементов последовательности, которые не находятся в очереди. Поэтому, если мы разобьем всю последовательность на блоки из <tex>m</tex> элементов (последний блок может быть меньше), и нам удастся обрабатывать каждый как перестановку из <tex>m</tex> элементов, сохраняя очередь <tex>B</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>S</tex> делится на блоки <tex>C_j,~j=1,~\dots,~\lceil\frac{n}{m}\rceil</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> отсортированный блок <tex>C_j</tex>. Отсортированные и неотсортированные блоки будем хранить в памяти. | |
| − | <tex>\ | + | [[Цифровая сортировка]] каждого блока отдельно будет давать нам время работы <tex>O \left(\dfrac{n}{m}n \right) = O \left(\dfrac{n^2}{m} \right)</tex>. Дополним каждый элемент <tex>\pi</tex> номером блока, в котором он находится и смещением в этом блоке. Теперь, рассматривая номер блока как старший разряд, элемент как младший разряд (по смещению внутри блока не сортируем), можно сортировать цифровой сортировкой за линейное время <tex>O(n)</tex>, потому что значения элементов и номера блоков не превосходят <tex>n</tex>. |
| − | <tex>\ | + | Перестановка смещений, образованная в сортированном блоке есть не что иное, как обратная перестановка перестановки <tex>\xi</tex>, элементы которой соотносятся между собой как элементы исходного блока. Т.е. если элемент <tex>\pi</tex> находится в исходной перестановке в блоке <tex>C_j</tex> на позиции <tex>i</tex>, то в блоке <tex>C_j^s</tex> он на позиции <tex>\xi_i</tex>. |
| − | |||
| − | |||
| − | + | ====Пример==== | |
| − | + | Предположим, что <tex>m=5</tex>. Исходно получаем: | |
| − | ==== | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
{| class="wikitable" style="text-align:center" | {| class="wikitable" style="text-align:center" | ||
| − | | Блок ||style="background:# | + | | Блок ||style="background:#FFC9C9"|<tex>1</tex>||style="background:#FFC9C9"|<tex>1</tex>||style="background:#FFC9C9"|<tex>1</tex>||style="background:#FFC9C9"|<tex>1</tex>||style="background:#FFC9C9"|<tex>1</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#CFCFFF"|<tex>3</tex>||style="background:#CFCFFF"|<tex>3</tex> |
|- | |- | ||
| − | |<tex>\pi</tex>||style="background:# | + | |<tex>\pi</tex>||style="background:#FFC9C9"|<tex>9</tex>||style="background:#FFC9C9"|<tex>3</tex>||style="background:#FFC9C9"|<tex>10</tex>||style="background:#FFC9C9"|<tex>4</tex>||style="background:#FFC9C9"|<tex>8</tex>||style="background:#B9FFB9"|<tex>1</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#B9FFB9"|<tex>12</tex>||style="background:#B9FFB9"|<tex>6</tex>||style="background:#B9FFB9"|<tex>5</tex>||style="background:#CFCFFF"|<tex>7</tex>||style="background:#CFCFFF"|<tex>11</tex> |
|- | |- | ||
| − | |Смещение||style="background:# | + | |Смещение ||style="background:#FFC9C9"|<tex>1</tex>||style="background:#FFC9C9"|<tex>2</tex>||style="background:#FFC9C9"|<tex>3</tex>||style="background:#FFC9C9"|<tex>4</tex>||style="background:#FFC9C9"|<tex>5</tex>||style="background:#B9FFB9"|<tex>1</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#B9FFB9"|<tex>3</tex>||style="background:#B9FFB9"|<tex>4</tex>||style="background:#B9FFB9"|<tex>5</tex>||style="background:#CFCFFF"|<tex>1</tex>||style="background:#CFCFFF"|<tex>2</tex> |
|} | |} | ||
| + | |||
После сортировки: | После сортировки: | ||
{| class="wikitable" style="text-align:center" | {| class="wikitable" style="text-align:center" | ||
| − | |Блок ||style="background:# | + | |Блок ||style="background:#FFC9C9"|<tex>1</tex>||style="background:#FFC9C9"|<tex>1</tex>||style="background:#FFC9C9"|<tex>1</tex>||style="background:#FFC9C9"|<tex>1</tex>||style="background:#FFC9C9"|<tex>1</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#CFCFFF"|<tex>3</tex>||style="background:#CFCFFF"|<tex>3</tex> |
|- | |- | ||
| − | |<tex>\pi</tex> ||style="background:# | + | |<tex>\pi</tex> ||style="background:#FFC9C9"|<tex>3</tex>||style="background:#FFC9C9"|<tex>4</tex>||style="background:#FFC9C9"|<tex>8</tex>||style="background:#FFC9C9"|<tex>9</tex>||style="background:#FFC9C9"|<tex>10</tex>||style="background:#B9FFB9"|<tex>1</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#B9FFB9"|<tex>5</tex>||style="background:#B9FFB9"|<tex>6</tex>||style="background:#B9FFB9"|<tex>12</tex>||style="background:#CFCFFF"|<tex>7</tex>||style="background:#CFCFFF"|<tex>11</tex> |
|- | |- | ||
| − | |Смещение ||style="background:# | + | |Смещение ||style="background:#FFC9C9"|<tex>2</tex>||style="background:#FFC9C9"|<tex>4</tex>||style="background:#FFC9C9"|<tex>5</tex>||style="background:#FFC9C9"|<tex>1</tex>||style="background:#FFC9C9"|<tex>3</tex>||style="background:#B9FFB9"|<tex>1</tex>||style="background:#B9FFB9"|<tex>2</tex>||style="background:#B9FFB9"|<tex>5</tex>||style="background:#B9FFB9"|<tex>4</tex>||style="background:#B9FFB9"|<tex>3</tex>||style="background:#CFCFFF"|<tex>1</tex>||style="background:#CFCFFF"|<tex>2</tex> |
|} | |} | ||
| + | |||
Обратные перестановки (<tex>\xi</tex>): | Обратные перестановки (<tex>\xi</tex>): | ||
{| class="wikitable" style="center" style="background:#FFCC80" | {| class="wikitable" style="center" style="background:#FFCC80" | ||
| − | ! colspan="5"|1 || colspan="5"|2 || colspan="3"|3 | + | ! colspan="5"|<tex>1</tex> || colspan="5"|<tex>2</tex> || colspan="3"|<tex>3</tex> |
|-align="center" | |-align="center" | ||
| − | | style="background:#FFD0D0"|4||style="background:#FFD0D0"|1||style="background:#FFD0D0"|5||style="background:#FFD0D0"|2||style="background:#FFD0D0"|3 | + | | style="background:#FFD0D0"|<tex>4</tex>||style="background:#FFD0D0"|<tex>1</tex>||style="background:#FFD0D0"|<tex>5</tex>||style="background:#FFD0D0"|<tex>2</tex>||style="background:#FFD0D0"|<tex>3</tex> |
| − | | style="background:#D0FFD0"|1||style="background:#D0FFD0"|2||style="background:#D0FFD0"|5||style="background:#D0FFD0"|4||style="background:#D0FFD0"|3 | + | | style="background:#D0FFD0"|<tex>1</tex>||style="background:#D0FFD0"|<tex>2</tex>||style="background:#D0FFD0"|<tex>5</tex>||style="background:#D0FFD0"|<tex>4</tex>||style="background:#D0FFD0"|<tex>3</tex> |
| − | | style="background:#D0D0FF"|1||style="background:#D0D0FF"|2 | + | | style="background:#D0D0FF"|<tex>1</tex>||style="background:#D0D0FF"|<tex>2</tex> |
|} | |} | ||
=== Обработка блока === | === Обработка блока === | ||
| − | == | + | Обрабатывая блок, каждому элементу <tex>x</tex> внутри этого блока взаимно однозначно сопоставим ключ <tex>y = \mathtt{key}(x);~x=\mathtt{elt}(y)</tex> так, чтобы их значения находились в промежутке <tex>\{1,2,\dots,2m\}</tex>. Очередь <tex>B</tex> будет работать непосредственно с ключами элементов. |
| − | + | ||
| + | Работая с блоком <tex>C_j</tex>, будем сливать элементы, ключи которых находятся в очереди <tex>B</tex>, с <tex>C_j^s</tex> в список <tex>\mathtt{merged}</tex>. Поскольку мы предположили, что <tex>m\geqslant k</tex>, то количество ключей в <tex>B</tex> не больше <tex>m</tex>, тогда длина <tex>\mathtt{merged}</tex> не больше <tex>2m</tex>, что позволяет однозначно определить ключи на множестве <tex>\{1,2,\dots,2m\}</tex>. Как было замечено ранее, элементы, чьи ключи находятся в <tex>B</tex>, располагаются в возрастающем порядке, поэтому возможно производить тривиальную операцию [[Сортировка слиянием#Принцип работы#Слияние двух массивов | слияния]] за <tex>O(m)</tex>. | ||
| + | |||
| + | В итоге, получим отсортированный список <tex>\mathtt{merged}</tex>. Сопоставим ключ каждому элементу как его позицию в этом списке, тогда справедливы утверждения, что <tex>\mathtt{elt}(x)=\mathtt{merged}[x]</tex> и <tex>(\pi_{i}<\pi_{k} \Longleftrightarrow \mathtt{key}(\pi_{i})<\mathtt{key}(\pi_{k}))</tex>, где <tex>\pi_{i},\pi_{k}\in \mathtt{merged}</tex>, поэтому любая возрастающая последовательность ключей элементов будет соответствовать возрастающей последовательности элементов. Таким образом, приоритетная очередь сможет корректно работать с ключами элементов. | ||
| + | |||
| + | Находим последовательность ключей, соответствующую элементам блока <tex>C_j^s</tex>. Действуя на эту последовательность перестановкой <tex>\xi_j</tex>, получаем последовательность ключей в порядке исходного блока. | ||
| − | + | Оставшиеся ключи, которые входят в <tex>\mathtt{merged}</tex>, но не являются ключами элементов в обрабатываемом блоке, будут ключами элементов из очереди <tex>B</tex>. Обновляем очередь <tex>B</tex> этими ключами. | |
| − | + | Затем запускаем алгоритм <tex>\mathrm{LIS}</tex>, для ключей элементов <tex>C_j</tex> в порядке исходной последовательности. | |
| − | * | + | В итоге, обработка блока делится на следующие этапы: |
| + | * Достаем из очереди <tex>B</tex> ключи <tex>x</tex>, конвертируем их в элементы <tex>\mathtt{elt}(x)</tex> и кладём в список <tex>\mathtt{elems}</tex>. | ||
| + | * Сливаем элементы в <tex>\mathtt{elems}</tex> со следующим отсортированным блоком <tex>C_j^s</tex> в список <tex>\mathtt{merged}</tex>, генерируя два вспомогательных массива <tex>\mathtt{ind_0}</tex> и <tex>\mathtt{ind_1}</tex>, хранящих индексы элементов списков <tex>C_j^s</tex> и <tex>\mathtt{elems}</tex> соответственно в списке <tex>\mathtt{merged}</tex>. | ||
| + | * Действуя на последовательность ключей в списке <tex>\mathtt{ind_0}</tex> перестановкой <tex>\xi_j</tex> получим ключи в порядке исходной последовательности. | ||
| + | * Вставляем в <tex>B</tex> новые ключи элементов списка <tex>\mathtt{elems}</tex> (элементы <tex>\mathtt{ind_1}</tex>). | ||
| + | * Обрабатываем ключи элементов блока в порядке исходной последовательности с помощью алгоритма <tex>\mathrm{LIS}</tex>. Для восстановления НВП также используем массив "предшественников", который будет работать с соответствующими ключам элементами <tex>\mathtt{elt}(x)</tex>. | ||
| − | ==== | + | ====Пример==== |
| − | |||
| − | + | ''' Первый блок ''' | |
| − | + | Так как очередь <tex>B</tex> в начале пуста, то <tex>\mathtt{merged}=C_1^s</tex>. Присвоим ключи элементам в списке <tex>\mathtt{merged}</tex> как их индексы в этом списке. Восстанавливаем последовательность ключей элементов в порядке исходной последовательности, действуя перестановкой смещений <tex>\xi_1</tex> на последовательность ключей в отсортированном блоке. | |
| − | |||
| − | |||
{| | {| | ||
| || | | || | ||
| − | {| style="center" | + | {| class="wikitable" style="text-align:center" |
| − | ! colspan=" | + | ! colspan="6"|Сортированный |
| − | |- | + | |- |
| − | | | + | | <tex>\pi</tex> ||<tex>3</tex>||<tex>4</tex>||<tex>8</tex>||<tex>9</tex>||<tex>10</tex> |
| − | |- | + | |- |
| − | | | + | | <tex>key</tex> ||<tex>1</tex>||<tex>2</tex>||<tex>3</tex>||<tex>4</tex>||<tex>5</tex> |
|} | |} | ||
| || | | || | ||
| − | {| style="center" | + | {| class="wikitable" style="text-align:center" |
| − | ! colspan=" | + | ! colspan="6"|Первый блок |
| − | |- | + | |- |
| − | | | + | | <tex>\pi</tex> ||<tex>9</tex>||<tex>3</tex>||<tex>10</tex>||<tex>4</tex>||<tex>8</tex> |
| − | |- | + | |- |
| − | | | + | | <tex>key</tex> ||<tex>4</tex>||<tex>1</tex>||<tex>5</tex>||<tex>2</tex>||<tex>3</tex> |
| + | |- | ||
| + | | <tex>\xi_1</tex> ||<tex>4</tex>||<tex>1</tex>||<tex>5</tex>||<tex>2</tex>||<tex>3</tex> | ||
|} | |} | ||
|} | |} | ||
| − | <tex>\ | + | |
| − | {| class="wikitable" style="center" | + | Обработка блока с помощью алгоритма <tex>\mathrm{LIS}</tex>. |
| − | + | ||
| − | + | {| class="wikitable" style="center"; style="background: #ffffcc" | |
| − | + | ! <tex>B_1</tex>||<tex>B_2</tex>||<tex>B_3</tex>||<tex>key</tex>||<tex>\pi</tex> | |
| − | |||
| − | |||
| − | |||
| − | |||
|-align="center" | |-align="center" | ||
| − | | style="background:# | + | | style="background:#FFC9C9"| <tex>4</tex> || || || style="background: #CFCFFF"| <tex>4</tex> || style="background: #B9FFB9"| <tex>9</tex> |
|-align="center" | |-align="center" | ||
| − | | style="background:# | + | | style="background:#FFC9C9"| <tex>1</tex> || || || style="background: #CFCFFF"| <tex>1</tex> || style="background: #B9FFB9"| <tex>3</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || style="background:# | + | | <tex>1</tex> || style="background:#FFC9C9"| <tex>5</tex> || || style="background: #CFCFFF"| <tex>5</tex> || style="background: #B9FFB9"| <tex>10</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || style="background:# | + | | <tex>1</tex> || style="background:#FFC9C9"| <tex>2</tex> || || style="background: #CFCFFF"| <tex>2</tex> || style="background: #B9FFB9"| <tex>4</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || style="background:#FFC9C9"| <tex>3</tex> || style="background: #CFCFFF"| <tex>3</tex> || style="background: #B9FFB9"| <tex>8</tex> |
|} | |} | ||
| − | + | ||
| + | В результате получаем | ||
<tex>B: \{1, 2, 3\}</tex> | <tex>B: \{1, 2, 3\}</tex> | ||
| − | <tex>\mathtt{merged}: \{3, 4, 8, 9, 10\}</tex> | + | <tex>\mathtt{merged}: \{3,4,8,9,10\}</tex> |
| + | |||
| + | |||
| + | '''Второй блок''' | ||
| − | |||
Восстанавливаем элементы <tex>B: \{1, 2, 3\}</tex> из <tex>\mathtt{merged}: \{3, 4, 8, 9, 10\}</tex>: <tex>\{3, 4, 8\}</tex>. | Восстанавливаем элементы <tex>B: \{1, 2, 3\}</tex> из <tex>\mathtt{merged}: \{3, 4, 8, 9, 10\}</tex>: <tex>\{3, 4, 8\}</tex>. | ||
| + | |||
| + | Сливаем <tex>C_2^s</tex> и восстановленные элементы из <tex>B</tex> в <tex>\mathtt{merged}</tex> и присваиваем элементам ключи как индексы элементов в полученном списке: | ||
{| | {| | ||
| || | | || | ||
| − | {| | + | {| class="wikitable" style="center" |
| − | |||
|-align="center" | |-align="center" | ||
| − | | | + | | colspan="3"|<tex>B</tex> |
|-align="center" | |-align="center" | ||
| − | | | + | | <tex>3</tex>||<tex>4</tex>||<tex>8</tex> |
|} | |} | ||
| || | | || | ||
| − | {| | + | {| class="wikitable" style="center" |
| − | |||
|-align="center" | |-align="center" | ||
| − | | | + | | colspan="5"|<tex>C_2^s</tex> |
|-align="center" | |-align="center" | ||
| − | | | + | | <tex>1</tex>||<tex>2</tex>||<tex>5</tex>||<tex>6</tex>||<tex>12</tex> |
|} | |} | ||
|} | |} | ||
| Строка 296: | Строка 297: | ||
{| class="wikitable" style="center" | {| class="wikitable" style="center" | ||
|-align="center" | |-align="center" | ||
| − | + | ! colspan="9"|<tex>\mathtt{merged}</tex> | |
|-align="center" | |-align="center" | ||
| − | | 1||2||3||4||5||6||8||12 | + | |<tex>\pi</tex>||<tex>1</tex>||<tex>2</tex>||<tex>3</tex>||<tex>4</tex>||<tex>5</tex>||<tex>6</tex>||<tex>8</tex>||<tex>12</tex> |
| + | |-align="center" | ||
| + | |<tex>key</tex>||<tex>1</tex>||<tex>2</tex>||<tex>3</tex>||<tex>4</tex>||<tex>5</tex>||<tex>6</tex>||<tex>7</tex>||<tex>8</tex> | ||
|} | |} | ||
| || | | || | ||
{| class="wikitable" style="center" | {| class="wikitable" style="center" | ||
|-align="center" | |-align="center" | ||
| − | | colspan="3"|<tex>\mathtt{ | + | | colspan="3"|<tex>\mathtt{ind_1}</tex> |
|-align="center" | |-align="center" | ||
| − | | 3||4||7 | + | | <tex>3</tex>||<tex>4</tex>||<tex>7</tex> |
|} | |} | ||
| || | | || | ||
{| class="wikitable" style="center" | {| class="wikitable" style="center" | ||
|-align="center" | |-align="center" | ||
| − | | colspan="5"|<tex>\mathtt{ | + | | colspan="5"|<tex>\mathtt{ind_0}</tex> |
|-align="center" | |-align="center" | ||
| − | | 1||2||5||6||8 | + | | <tex>1</tex>||<tex>2</tex>||<tex>5</tex>||<tex>6</tex>||<tex>8</tex> |
|} | |} | ||
|} | |} | ||
| − | {| class="wikitable" style="center" | + | Получаем ключи элементов в <tex>C_2^s</tex> и находим перестановку ключей в порядке исходной последовательности, действуя перестановкой <tex>\xi_2</tex>: |
| − | ! colspan="6"| | + | |
| − | |-align=" | + | {| |
| − | | | + | | || |
| − | |- | + | {| class="wikitable" style="text-align:center" |
| − | | <tex>\ | + | ! colspan="6"|Сортированный |
| + | |- | ||
| + | | <tex>\pi</tex> ||<tex>1</tex>||<tex>2</tex>||<tex>5</tex>||<tex>6</tex>||<tex>12</tex> | ||
| + | |- | ||
| + | | <tex>key</tex> ||<tex>1</tex>||<tex>2</tex>||<tex>5</tex>||<tex>6</tex>||<tex>8</tex> | ||
| + | |} | ||
| + | | || | ||
| + | {| class="wikitable" style="text-align:center" | ||
| + | ! colspan="6"|Второй блок | ||
| + | |- | ||
| + | | <tex>\pi</tex> ||<tex>1</tex>||<tex>2</tex>||<tex>12</tex>||<tex>6</tex>||<tex>5</tex> | ||
| + | |- | ||
| + | | <tex>key</tex> ||<tex>1</tex>||<tex>2</tex>||<tex>8</tex>||<tex>6</tex>||<tex>5</tex> | ||
| + | |- | ||
| + | | <tex>\xi_2</tex> ||<tex>1</tex>||<tex>2</tex>||<tex>5</tex>||<tex>4</tex>||<tex>3</tex> | ||
|} | |} | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
|} | |} | ||
| − | + | ||
| − | {| class="wikitable" style="center" | + | Обновляем ключи в очереди: |
| − | ! <tex>B_1</tex>||<tex>B_2</tex>||<tex>B_3</tex>||<tex> | + | {| class="wikitable" style="center" style="background: #ffffcc" |
| + | ! <tex>B_1</tex>||<tex>B_2</tex>||<tex>B_3</tex>||<tex>key</tex> | ||
|-align="center" | |-align="center" | ||
| − | | style="background:# | + | | style="background:#FFC9C9"| <tex>3</tex> || || || style="background: #CFCFFF"| <tex>3</tex> |
|-align="center" | |-align="center" | ||
| − | | 3 || style="background:# | + | | <tex>3</tex> || style="background:#FFC9C9"| <tex>4</tex> || || style="background: #CFCFFF"| <tex>4</tex> |
|-align="center" | |-align="center" | ||
| − | | 3 || 4 || style="background:# | + | | <tex>3</tex> || <tex>4</tex> || style="background:#FFC9C9"| <tex>7</tex> || style="background: #CFCFFF"| <tex>7</tex> |
|} | |} | ||
| − | <tex>\mathrm{LIS}</tex> | + | |
| − | {| class="wikitable" style="center" | + | запускаем <tex>\mathrm{LIS}</tex> для блока: |
| − | ! <tex>B_1</tex>||<tex>B_2</tex>||<tex>B_3</tex>||<tex>B_4</tex>||<tex>\pi</tex> | + | {| class="wikitable" style="center" style="background: #ffffcc" |
| + | ! <tex>B_1</tex>||<tex>B_2</tex>||<tex>B_3</tex>||<tex>B_4</tex>||<tex>key</tex>||<tex>\pi</tex> | ||
|-align="center" | |-align="center" | ||
| − | | style="background:# | + | | style="background:#FFC9C9"| <tex>1</tex> || <tex>4</tex> || <tex>7</tex> || || style="background: #CFCFFF"| <tex>1</tex> || style="background: #B9FFB9"| <tex>1</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || style="background:# | + | | <tex>1</tex> || style="background:#FFC9C9"| <tex>2</tex> || <tex>7</tex> || || style="background: #CFCFFF"| <tex>2</tex> || style="background: #B9FFB9"| <tex>2</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || 7 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || <tex>7</tex> || style="background:#FFC9C9"| <tex>8</tex> || style="background: #CFCFFF"| <tex>8</tex> || style="background: #B9FFB9"| <tex>12</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || style="background:#FFC9C9"| <tex>6</tex> || <tex>8</tex> || style="background: #CFCFFF"| <tex>6</tex> || style="background: #B9FFB9"| <tex>6</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || style="background:#FFC9C9"| <tex>5</tex> || <tex>8</tex> || style="background: #CFCFFF"| <tex>5</tex> || style="background: #B9FFB9"| <tex>5</tex> |
|} | |} | ||
| − | + | ||
| + | В результате получаем: | ||
<tex>B: \{1, 2, 5, 8\}</tex> | <tex>B: \{1, 2, 5, 8\}</tex> | ||
| Строка 359: | Строка 374: | ||
<tex>\mathtt{merged}: \{1,2,3,4,5,6,8,12\}</tex> | <tex>\mathtt{merged}: \{1,2,3,4,5,6,8,12\}</tex> | ||
| − | + | ||
| − | Восстанавливаем элементы <tex>B: \{1, 2, 5, 8\}</tex> из <tex>\mathtt{merged}: \{1,2,3,4,5,6,8,12\}</tex>: <tex>\{1, 2, 5, 12\}</tex>. | + | '''Третий блок''' |
| + | |||
| + | Восстанавливаем элементы <tex>B: \{1, 2, 5, 8\}</tex> из <tex>\mathtt{merged}: \{1, 2, 3, 4, 5, 6, 8, 12\}</tex>: <tex>\{1, 2, 5, 12\}</tex>. | ||
| + | |||
| + | Сливаем <tex>C_3^s</tex> и восстановленные элементы из <tex>B</tex> и присваиваем ключи элементам: | ||
{| | {| | ||
| || | | || | ||
| − | {| | + | {| class="wikitable" style="center" |
| − | |||
|-align="center" | |-align="center" | ||
| − | | | + | | colspan="4"|<tex>B</tex> |
|-align="center" | |-align="center" | ||
| − | | | + | | <tex>1</tex>||<tex>2</tex>||<tex>5</tex>||<tex>12</tex> |
|} | |} | ||
| || | | || | ||
| − | {| | + | {| class="wikitable" style="center" |
| − | |||
|-align="center" | |-align="center" | ||
| − | | | + | | colspan="2"|<tex>C_3^s</tex> |
|-align="center" | |-align="center" | ||
| − | | | + | | <tex>7</tex>||<tex>11</tex> |
|} | |} | ||
|} | |} | ||
| Строка 384: | Строка 401: | ||
{| class="wikitable" style="center" | {| class="wikitable" style="center" | ||
|-align="center" | |-align="center" | ||
| − | + | ! colspan="7"|<tex>\mathtt{merged}</tex> | |
|-align="center" | |-align="center" | ||
| − | | 1||2||5||7||11||12 | + | |<tex>\pi</tex>||<tex>1</tex>||<tex>2</tex>||<tex>5</tex>||<tex>7</tex>||<tex>11</tex>||<tex>12</tex> |
| + | |-align="center" | ||
| + | |<tex>key</tex>||<tex>1</tex>||<tex>2</tex>||<tex>3</tex>||<tex>4</tex>||<tex>5</tex>||<tex>6</tex> | ||
|} | |} | ||
| || | | || | ||
{| class="wikitable" style="center" | {| class="wikitable" style="center" | ||
|-align="center" | |-align="center" | ||
| − | | colspan="4"|<tex>\mathtt{ | + | | colspan="4"|<tex>\mathtt{ind_1}</tex> |
|-align="center" | |-align="center" | ||
| − | | 1||2||3||6 | + | | <tex>1</tex>||<tex>2</tex>||<tex>3</tex>||<tex>6</tex> |
|} | |} | ||
| || | | || | ||
{| class="wikitable" style="center" | {| class="wikitable" style="center" | ||
|-align="center" | |-align="center" | ||
| − | | colspan="2"|<tex>\mathtt{ | + | | colspan="2"|<tex>\mathtt{ind_0}</tex> |
|-align="center" | |-align="center" | ||
| − | | 4||5 | + | | <tex>4</tex>||<tex>5</tex> |
|} | |} | ||
|} | |} | ||
| − | {| class="wikitable" style="center" | + | |
| − | ! colspan=" | + | Получаем ключи элементов в <tex>C_3^s</tex> и находим перестановку ключей в порядке исходной последовательности, действуя перестановкой <tex>\xi_3</tex>: |
| − | |- | + | |
| − | | | + | {| |
| + | | || | ||
| + | {| | ||
| + | | || | ||
| + | {| class="wikitable" style="text-align:center" | ||
| + | ! colspan="3"| Третий блок | ||
| + | |- | ||
| + | | <tex>\pi</tex> ||<tex>7</tex>||<tex>11</tex> | ||
| + | |- | ||
| + | | <tex>key</tex> ||<tex>4</tex>||<tex>5</tex> | ||
|} | |} | ||
| − | {| class="wikitable" style="center" | + | | || |
| − | ! colspan=" | + | {| class="wikitable" style="text-align:center" |
| − | |- | + | ! colspan="3"|Cортированный |
| − | | 1||2 | + | |- |
| + | | <tex>\pi</tex> ||<tex>7</tex>||<tex>11</tex> | ||
| + | |- | ||
| + | | <tex>key</tex> ||<tex>4</tex>||<tex>5</tex> | ||
| + | |- | ||
| + | | <tex>\xi_3</tex> ||<tex>1</tex>||<tex>2</tex> | ||
|} | |} | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
|} | |} | ||
| + | |||
Обновление старых ключей: | Обновление старых ключей: | ||
| − | {| class="wikitable" style="center" | + | {| class="wikitable" style="center" style="background: #ffffcc" |
| + | ! <tex>B_1</tex>||<tex>B_2</tex>||<tex>B_3</tex>||<tex>B_4</tex>||<tex>key</tex> | ||
|-align="center" | |-align="center" | ||
| − | | style="background:# | + | | style="background:#FFC9C9"| <tex>1</tex> || || || || style="background: #CFCFFF"| <tex>1</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || style="background:# | + | | <tex>1</tex> || style="background:#FFC9C9"| <tex>2</tex> || || || style="background: #CFCFFF"| <tex>2</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || style="background:#FFC9C9"| <tex>3</tex> || || style="background: #CFCFFF"| <tex>3</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || 3 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || <tex>3</tex> || style="background:#FFC9C9"| <tex>6</tex> || style="background: #CFCFFF"| <tex>6</tex> |
|} | |} | ||
| − | <tex>LIS</tex> | + | |
| − | {| class="wikitable" style="center" | + | запускаем <tex>\mathrm{LIS}</tex> для блока: |
| + | {| class="wikitable" style="center" style="background: #ffffcc" | ||
| + | ! <tex>B_1</tex>||<tex>B_2</tex>||<tex>B_3</tex>||<tex>B_4</tex>||<tex>B_5</tex>||<tex>key</tex>||<tex>\pi</tex> | ||
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || 3 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || <tex>3</tex> || style="background:#FFC9C9"| <tex>4</tex> || || style="background: #CFCFFF"| <tex>4</tex> || style="background: #B9FFB9"| <tex>7</tex> |
|-align="center" | |-align="center" | ||
| − | | 1 || 2 || 3 || 4 || style="background:# | + | | <tex>1</tex> || <tex>2</tex> || <tex>3</tex> || <tex>4</tex> || style="background:#FFC9C9"| <tex>5</tex> || style="background: #CFCFFF"| <tex>5</tex> || style="background: #B9FFB9"| <tex>11</tex> |
|} | |} | ||
| − | + | Результат завершения алгоритма: | |
<tex>B: \{1, 2, 3, 4, 5\}</tex> | <tex>B: \{1, 2, 3, 4, 5\}</tex> | ||
| Строка 443: | Строка 475: | ||
<tex>\mathtt{merged}: \{1,2,5,7,11,12\}</tex> | <tex>\mathtt{merged}: \{1,2,5,7,11,12\}</tex> | ||
| − | = | + | Получаем, что длина НВП — <tex>5</tex>, и НВП оканчивается на <tex>\mathtt{merged}[5]=11</tex>. |
| + | |||
| + | '''Восстановление НВП''' | ||
{| class="wikitable" style="center" | {| class="wikitable" style="center" | ||
! colspan="12"| <tex>\mathtt{predecessor}</tex> | ! colspan="12"| <tex>\mathtt{predecessor}</tex> | ||
|-align="center" | |-align="center" | ||
| − | | 1||2||3||4||5||6||7||8||9||10||11||12 | + | | style="background:#DCFFFF"|<tex>1</tex>||style="background:#DCDCFF"|<tex>2</tex>||<tex>3</tex>||<tex>4</tex>||style="background:#DCFFDC"|<tex>5</tex>||<tex>6</tex>||style="background:#FFDCDC"|<tex>7</tex>||<tex>8</tex>||<tex>9</tex>||<tex>10</tex>||style="background:#FFFFC8"|<tex>11</tex>||<tex>12</tex> |
|-align="center" | |-align="center" | ||
| − | | ||1|| ||3||2||2||5||4|| ||3||7||8 | + | |style="background:#FFFFC8"| ||style="background:#DCFFFF"|<tex>1</tex>|| ||<tex>3</tex>||style="background:#DCDCFF"|<tex>2</tex>||style="background:#E6E6FF"|<tex>2</tex>||style="background:#DCFFDC"|<tex>5</tex>||<tex>4</tex>|| ||<tex>3</tex>||style="background:#FFDCDC"|<tex>7</tex>||<tex>8</tex> |
|} | |} | ||
Начинаем восстановление с <tex>\mathtt{merged}[5] = 11</tex>: | Начинаем восстановление с <tex>\mathtt{merged}[5] = 11</tex>: | ||
{| class="wikitable" style="center" | {| class="wikitable" style="center" | ||
| − | | | + | |-align="center" |
| − | | 11||7||5||2||1 | + | | обратный порядок||style="background:#FFFFC8"|<tex>11</tex>||style="background:#FFDCDC"|<tex>7</tex>||style="background:#DCFFDC"|<tex>5</tex>||style="background:#E6E6FF"|<tex>2</tex>||style="background:#DCFFFF"|<tex>1</tex> |
| − | | | + | |-align="center" |
| − | + | | НВП||style="background:#DCFFFF"|<tex>1</tex>||style="background:#E6E6FF"|<tex>2</tex>||style="background:#DCFFDC"|<tex>5</tex>||style="background:#FFDCDC"|<tex>7</tex>||style="background:#FFFFC8"|<tex>11</tex> | |
| − | |||
| − | | 1||2||5||7||11 | ||
|} | |} | ||
| + | |||
| + | === Нахождение размера блоков === | ||
| + | |||
| + | Рассмотрим последовательность <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>B</tex> становится больше <tex>m_i</tex>, то условие <tex>m \geqslant k</tex> перестает выполняться, тогда останавливаем алгоритм и переходим к следующему значению <tex>m_{i+1}</tex>. | ||
| + | |||
| + | Для каждого <tex>m_i</tex> размер списка <tex>\mathtt{merged}</tex> не больше <tex>2m_i</tex>, а количество блоков всего <tex>\lceil n/m_i \rceil</tex>. То общее количество присваиваний новых ключей элементам последовательности, также как и количество операций слияния списков, не больше <tex>2cm_i\cdot\dfrac{n}{m_i}=O(n)</tex>, где c — некоторая константа. Каждая операция с приоритетной очередью требует <tex>O(\log \log m_i)</tex> времени, так как элементы в <tex>B</tex> не больше <tex>2m_i</tex>. | ||
| + | |||
| + | Таким образом, время работы запущенного алгоритма для каждого <tex>m_i</tex> — <tex>O(n \log \log {m_i})</tex>. Когда найдётся первое <tex>m_j:m_j\geqslant 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_j = 2^{j-i}\operatorname{log}\operatorname{log}m_i</tex> | ||
| + | |||
| + | Общее время работы алгоритма для всех обработанных значений <tex>m_i</tex> — <tex>O(n(\sum_{i=0}\limits^{j}{2^{-(i-1)}})\log \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>. | ||
== См. также == | == См. также == | ||
Текущая версия на 19:07, 4 сентября 2022
| Задача: |
| Дана перестановка множества . Требуется найти НВП за , где — длина НВП. |
Содержание
Алгоритм за O(n log log n)
Нахождение длины НВП
Основная идея
Пусть — входная перестановка.
Будем последовательно обрабатывать элементы в порядке
Для каждой длины предполагаемой НВП находим наименьший элемент, который может быть последним в возрастающей подпоследовательности длины и запишем его в массив . Будем называть его наилучшим элементом для длины .
- Если больше каждого элемента , вычисленного для подпоследовательности , тогда с ним можно сделать возрастающую подпоследовательность максимальной длины из уже рассмотренных, в которой он будет последним элементом. Значит, записываем его в конец .
- Иначе будет наилучшим элементом для уже существующей длины и сможет улучшить только один элемент в , тогда мы находим наименьшее и заменяем элементом .
Следует отметить, что полученный массив также образует возрастающую последовательность, на котором мы должны выполнять операции , соответственно целесообразно использовать приоритетную очередь, реализованную через Дерево ван Эмде Боаса. Так как данная структура данных производит описанные операции за , где k — количество бит чисел, которые позволяет хранить дерево, то полученный алгоритм работает за , потому что все элементы последовательности не превосходят n.
Пример
Типы операций
- Добавление элемента, который больше всех предыдущих:
- Замещение элемента более подходящим, т.е. добавление немаксимального элемента:
Пример последовательности
Состояние очереди при каждом добавлении
Псевдокод
int LIS([n]) PriorityQueue B // рабочая приоритетная очередь int k = 0 // длина НВП for i = 1 to n x = [i] // в любом случае добавляем в очередь очередной элемент // устаревшие будем удалять B.insert(x) if B.next(x) // добавленный элемент — не максимальный // удаляем следующее за x значение B.delete(B.next(x)) else // добавленный элемент — максимальный // предыдущие значения не трогаем, очередь увеличилась k = k + 1 return k
Расширение алгоритма до нахождения НВП
Основная идея
Будем запоминать пары: для каждого элемента записываем его "предшественника".
Тогда, пройдя по предшественникам, начиная с последнего элемента очереди , мы можем восстановить НВП.
Общий вид алгоритма
| predecessor | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
Псевдокод
int[] LIS([n]) PriorityQueue B int k = 0 int predecessor[n] // резервируем позиций for i = 1 to n x = [i] B.insert(x) predecessor[x] = B.prev(x) if B.next(x) B.delete(B.next(x)) else k = k + 1 // по цепочке от последнего элемента // восстанавливаем НВП int result[k] int cur = B.max for i = k - 1 downto 0 result[i] = cur cur = predecessor[cur] return result
Оптимизация до O(n log log k)
Основная идея
Чтобы Дерево ван Эмде Боаса выполняло операции за , необходимо алфавит обрабатываемых значений уменьшить до .
Предположим, мы знаем такое приближение числа числом . Мы обсудим, как найти такое позже.
Во время обработки ключей элементов описанный выше алгоритм работает только с очередью и не зависит от предыдущих элементов последовательности, которые не находятся в очереди. Поэтому, если мы разобьем всю последовательность на блоки из элементов (последний блок может быть меньше), и нам удастся обрабатывать каждый как перестановку из элементов, сохраняя очередь для вычисленных ранее блоков, то мы получим асимптотическое время , а так как , то . (Мы будем обрабатывать блоки последовательно, т.е. с предыдущего блока у нас может остаться значений в очереди, которые дополняются значениями очередного блока — получаем верхнее ограничение в обрабатываемых возможных значений.)
Деление на блоки
Последовательность делится на блоки :
Обозначим за отсортированный блок . Отсортированные и неотсортированные блоки будем хранить в памяти.
Цифровая сортировка каждого блока отдельно будет давать нам время работы . Дополним каждый элемент номером блока, в котором он находится и смещением в этом блоке. Теперь, рассматривая номер блока как старший разряд, элемент как младший разряд (по смещению внутри блока не сортируем), можно сортировать цифровой сортировкой за линейное время , потому что значения элементов и номера блоков не превосходят .
Перестановка смещений, образованная в сортированном блоке есть не что иное, как обратная перестановка перестановки , элементы которой соотносятся между собой как элементы исходного блока. Т.е. если элемент находится в исходной перестановке в блоке на позиции , то в блоке он на позиции .
Пример
Предположим, что . Исходно получаем:
| Блок | ||||||||||||
| Смещение |
После сортировки:
| Блок | ||||||||||||
| Смещение |
Обратные перестановки ():
Обработка блока
Обрабатывая блок, каждому элементу внутри этого блока взаимно однозначно сопоставим ключ так, чтобы их значения находились в промежутке . Очередь будет работать непосредственно с ключами элементов.
Работая с блоком , будем сливать элементы, ключи которых находятся в очереди , с в список . Поскольку мы предположили, что , то количество ключей в не больше , тогда длина не больше , что позволяет однозначно определить ключи на множестве . Как было замечено ранее, элементы, чьи ключи находятся в , располагаются в возрастающем порядке, поэтому возможно производить тривиальную операцию слияния за .
В итоге, получим отсортированный список . Сопоставим ключ каждому элементу как его позицию в этом списке, тогда справедливы утверждения, что и , где , поэтому любая возрастающая последовательность ключей элементов будет соответствовать возрастающей последовательности элементов. Таким образом, приоритетная очередь сможет корректно работать с ключами элементов.
Находим последовательность ключей, соответствующую элементам блока . Действуя на эту последовательность перестановкой , получаем последовательность ключей в порядке исходного блока.
Оставшиеся ключи, которые входят в , но не являются ключами элементов в обрабатываемом блоке, будут ключами элементов из очереди . Обновляем очередь этими ключами.
Затем запускаем алгоритм , для ключей элементов в порядке исходной последовательности.
В итоге, обработка блока делится на следующие этапы:
- Достаем из очереди ключи , конвертируем их в элементы и кладём в список .
- Сливаем элементы в со следующим отсортированным блоком в список , генерируя два вспомогательных массива и , хранящих индексы элементов списков и соответственно в списке .
- Действуя на последовательность ключей в списке перестановкой получим ключи в порядке исходной последовательности.
- Вставляем в новые ключи элементов списка (элементы ).
- Обрабатываем ключи элементов блока в порядке исходной последовательности с помощью алгоритма . Для восстановления НВП также используем массив "предшественников", который будет работать с соответствующими ключам элементами .
Пример
Первый блок
Так как очередь в начале пуста, то . Присвоим ключи элементам в списке как их индексы в этом списке. Восстанавливаем последовательность ключей элементов в порядке исходной последовательности, действуя перестановкой смещений на последовательность ключей в отсортированном блоке.
|
| ||||||||||||||||||||||||||||||||||||||||||||
Обработка блока с помощью алгоритма .
В результате получаем
Второй блок
Восстанавливаем элементы из : .
Сливаем и восстановленные элементы из в и присваиваем элементам ключи как индексы элементов в полученном списке:
|
|
| ||||||||||||||||||
|
|
|
| ||||||||||||||||||||||||||||||||||||||||||||||
Получаем ключи элементов в и находим перестановку ключей в порядке исходной последовательности, действуя перестановкой :
|
| ||||||||||||||||||||||||||||||||||||||||||||
Обновляем ключи в очереди:
запускаем для блока:
В результате получаем:
Третий блок
Восстанавливаем элементы из : .
Сливаем и восстановленные элементы из и присваиваем ключи элементам:
|
|
| ||||||||||||||
|
|
|
| ||||||||||||||||||||||||||||||||||||
Получаем ключи элементов в и находим перестановку ключей в порядке исходной последовательности, действуя перестановкой :
Обновление старых ключей: запускаем для блока: Результат завершения алгоритма:
Получаем, что длина НВП — , и НВП оканчивается на . Восстановление НВП Начинаем восстановление с :
Нахождение размера блоковРассмотрим последовательность , где , — некоторое значение, меньшее . Будем последовательно для элементов этой последовательности запускать алгоритм, представленный выше. Если размер очереди становится больше , то условие перестает выполняться, тогда останавливаем алгоритм и переходим к следующему значению . Для каждого размер списка не больше , а количество блоков всего . То общее количество присваиваний новых ключей элементам последовательности, также как и количество операций слияния списков, не больше , где c — некоторая константа. Каждая операция с приоритетной очередью требует времени, так как элементы в не больше . Таким образом, время работы запущенного алгоритма для каждого — . Когда найдётся первое , то алгоритм успешно завершится. .
Общее время работы алгоритма для всех обработанных значений — . Заметим, что , так как в противном случае , что противоречит тому, что — первый из тех, которые больше . Следовательно, . Получаем время работы . См. также
Источники информации |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||



