Быстрый поиск наибольшей возрастающей подпоследовательности — различия между версиями
м (→Обработка блока) |
м (rollbackEdits.php mass rollback) |
||
(не показано 30 промежуточных версий 5 участников) | |||
Строка 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) == |
=== Нахождение длины НВП === | === Нахождение длины НВП === | ||
==== Основная идея ==== | ==== Основная идея ==== | ||
− | Пусть <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. | |
− | |||
− | |||
==== Пример ==== | ==== Пример ==== | ||
− | Типы операций: | + | ''' Типы операций ''' |
+ | |||
+ | * Добавление элемента, который больше всех предыдущих: | ||
[[Файл:Operation1.jpg]] | [[Файл:Operation1.jpg]] | ||
+ | |||
+ | * Замещение элемента более подходящим, т.е. добавление немаксимального элемента: | ||
[[Файл: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 | |
− | |||
=== Расширение алгоритма до нахождения НВП === | === Расширение алгоритма до нахождения НВП === | ||
Строка 90: | Строка 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" | ||
Строка 96: | Строка 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) == | |
− | + | ===Основная идея=== | |
− | == Оптимизация до | ||
− | === Основная идея === | ||
Чтобы [[Дерево ван Эмде Боаса]] выполняло операции за <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>): | ||
+ | {| class="wikitable" style="center" style="background:#FFCC80" | ||
+ | ! colspan="5"|<tex>1</tex> || colspan="5"|<tex>2</tex> || colspan="3"|<tex>3</tex> | ||
+ | |-align="center" | ||
+ | | 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"|<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"|<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> на последовательность ключей в отсортированном блоке. | ||
+ | |||
+ | {| | ||
+ | | || | ||
+ | {| class="wikitable" style="text-align:center" | ||
+ | ! 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> | ||
+ | |} | ||
+ | | || | ||
+ | {| class="wikitable" style="text-align:center" | ||
+ | ! 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>\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" | ||
+ | | style="background:#FFC9C9"| <tex>4</tex> || || || style="background: #CFCFFF"| <tex>4</tex> || style="background: #B9FFB9"| <tex>9</tex> | ||
+ | |-align="center" | ||
+ | | style="background:#FFC9C9"| <tex>1</tex> || || || style="background: #CFCFFF"| <tex>1</tex> || style="background: #B9FFB9"| <tex>3</tex> | ||
+ | |-align="center" | ||
+ | | <tex>1</tex> || style="background:#FFC9C9"| <tex>5</tex> || || style="background: #CFCFFF"| <tex>5</tex> || style="background: #B9FFB9"| <tex>10</tex> | ||
+ | |-align="center" | ||
+ | | <tex>1</tex> || style="background:#FFC9C9"| <tex>2</tex> || || style="background: #CFCFFF"| <tex>2</tex> || style="background: #B9FFB9"| <tex>4</tex> | ||
+ | |-align="center" | ||
+ | | <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>\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>C_2^s</tex> и восстановленные элементы из <tex>B</tex> в <tex>\mathtt{merged}</tex> и присваиваем элементам ключи как индексы элементов в полученном списке: | ||
+ | {| | ||
+ | | || | ||
+ | {| class="wikitable" style="center" | ||
+ | |-align="center" | ||
+ | | colspan="3"|<tex>B</tex> | ||
+ | |-align="center" | ||
+ | | <tex>3</tex>||<tex>4</tex>||<tex>8</tex> | ||
+ | |} | ||
+ | | || | ||
+ | {| class="wikitable" style="center" | ||
+ | |-align="center" | ||
+ | | colspan="5"|<tex>C_2^s</tex> | ||
+ | |-align="center" | ||
+ | | <tex>1</tex>||<tex>2</tex>||<tex>5</tex>||<tex>6</tex>||<tex>12</tex> | ||
+ | |} | ||
+ | |} | ||
+ | |||
+ | {| | ||
+ | | || | ||
+ | {| class="wikitable" style="center" | ||
+ | |-align="center" | ||
+ | ! colspan="9"|<tex>\mathtt{merged}</tex> | ||
+ | |-align="center" | ||
+ | |<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" | ||
+ | |-align="center" | ||
+ | | colspan="3"|<tex>\mathtt{ind_1}</tex> | ||
+ | |-align="center" | ||
+ | | <tex>3</tex>||<tex>4</tex>||<tex>7</tex> | ||
+ | |} | ||
+ | | || | ||
+ | {| class="wikitable" style="center" | ||
+ | |-align="center" | ||
+ | | colspan="5"|<tex>\mathtt{ind_0}</tex> | ||
+ | |-align="center" | ||
+ | | <tex>1</tex>||<tex>2</tex>||<tex>5</tex>||<tex>6</tex>||<tex>8</tex> | ||
+ | |} | ||
+ | |} | ||
+ | |||
+ | Получаем ключи элементов в <tex>C_2^s</tex> и находим перестановку ключей в порядке исходной последовательности, действуя перестановкой <tex>\xi_2</tex>: | ||
+ | |||
+ | {| | ||
+ | | || | ||
+ | {| class="wikitable" style="text-align:center" | ||
+ | ! 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" style="background: #ffffcc" | ||
+ | ! <tex>B_1</tex>||<tex>B_2</tex>||<tex>B_3</tex>||<tex>key</tex> | ||
+ | |-align="center" | ||
+ | | style="background:#FFC9C9"| <tex>3</tex> || || || style="background: #CFCFFF"| <tex>3</tex> | ||
+ | |-align="center" | ||
+ | | <tex>3</tex> || style="background:#FFC9C9"| <tex>4</tex> || || style="background: #CFCFFF"| <tex>4</tex> | ||
+ | |-align="center" | ||
+ | | <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" 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" | ||
+ | | 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" | ||
+ | | <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" | ||
+ | | <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" | ||
+ | | <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" | ||
+ | | <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>\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>C_3^s</tex> и восстановленные элементы из <tex>B</tex> и присваиваем ключи элементам: | ||
+ | {| | ||
+ | | || | ||
+ | {| class="wikitable" style="center" | ||
+ | |-align="center" | ||
+ | | colspan="4"|<tex>B</tex> | ||
+ | |-align="center" | ||
+ | | <tex>1</tex>||<tex>2</tex>||<tex>5</tex>||<tex>12</tex> | ||
+ | |} | ||
+ | | || | ||
+ | {| class="wikitable" style="center" | ||
+ | |-align="center" | ||
+ | | colspan="2"|<tex>C_3^s</tex> | ||
+ | |-align="center" | ||
+ | | <tex>7</tex>||<tex>11</tex> | ||
+ | |} | ||
+ | |} | ||
+ | |||
+ | {| | ||
+ | | || | ||
+ | {| class="wikitable" style="center" | ||
+ | |-align="center" | ||
+ | ! colspan="7"|<tex>\mathtt{merged}</tex> | ||
+ | |-align="center" | ||
+ | |<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" | ||
+ | |-align="center" | ||
+ | | colspan="4"|<tex>\mathtt{ind_1}</tex> | ||
+ | |-align="center" | ||
+ | | <tex>1</tex>||<tex>2</tex>||<tex>3</tex>||<tex>6</tex> | ||
+ | |} | ||
+ | | || | ||
+ | {| class="wikitable" style="center" | ||
+ | |-align="center" | ||
+ | | colspan="2"|<tex>\mathtt{ind_0}</tex> | ||
+ | |-align="center" | ||
+ | | <tex>4</tex>||<tex>5</tex> | ||
+ | |} | ||
+ | |} | ||
+ | |||
+ | Получаем ключи элементов в <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="text-align:center" | ||
+ | ! colspan="3"|Cортированный | ||
+ | |- | ||
+ | | <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" style="background: #ffffcc" | ||
+ | ! <tex>B_1</tex>||<tex>B_2</tex>||<tex>B_3</tex>||<tex>B_4</tex>||<tex>key</tex> | ||
+ | |-align="center" | ||
+ | | style="background:#FFC9C9"| <tex>1</tex> || || || || style="background: #CFCFFF"| <tex>1</tex> | ||
+ | |-align="center" | ||
+ | | <tex>1</tex> || style="background:#FFC9C9"| <tex>2</tex> || || || style="background: #CFCFFF"| <tex>2</tex> | ||
+ | |-align="center" | ||
+ | | <tex>1</tex> || <tex>2</tex> || style="background:#FFC9C9"| <tex>3</tex> || || style="background: #CFCFFF"| <tex>3</tex> | ||
+ | |-align="center" | ||
+ | | <tex>1</tex> || <tex>2</tex> || <tex>3</tex> || style="background:#FFC9C9"| <tex>6</tex> || style="background: #CFCFFF"| <tex>6</tex> | ||
+ | |} | ||
+ | |||
+ | запускаем <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" | ||
+ | | <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" | ||
+ | | <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>\mathtt{merged}: \{1,2,5,7,11,12\}</tex> | ||
+ | |||
+ | Получаем, что длина НВП — <tex>5</tex>, и НВП оканчивается на <tex>\mathtt{merged}[5]=11</tex>. | ||
+ | |||
+ | '''Восстановление НВП''' | ||
+ | {| class="wikitable" style="center" | ||
+ | ! colspan="12"| <tex>\mathtt{predecessor}</tex> | ||
+ | |-align="center" | ||
+ | | 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" | ||
+ | |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>: | ||
+ | {| class="wikitable" style="center" | ||
+ | |-align="center" | ||
+ | | обратный порядок||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> | ||
+ | |} | ||
+ | |||
+ | === Нахождение размера блоков === | ||
+ | |||
+ | Рассмотрим последовательность <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>. | |
− | ==== | + | == См. также == |
+ | *[[Дерево ван Эмде Боаса]] | ||
+ | *[[Задача о наибольшей возрастающей подпоследовательности]] | ||
+ | *[[Задача о наибольшей общей подпоследовательности]] | ||
+ | *[[Наибольшая общая возрастающая подпоследовательность]] | ||
+ | *[[Задача о наибольшей общей палиндромной подпоследовательности]] | ||
+ | |||
+ | == Источники информации == | ||
+ | * [http://www.bcs.org/upload/pdf/ewic_vs08_s2paper2.pdf Computing a Longest Increasing Subsequence of Length k in Time O(n log log k)] (07.01.2017) | ||
− | + | [[Категория: Дискретная математика и алгоритмы]] | |
+ | [[Категория: Динамическое программирование]] | ||
+ | [[Категория: Классические задачи динамического программирования]] |
Текущая версия на 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 — некоторая константа. Каждая операция с приоритетной очередью требует времени, так как элементы в не больше .Таким образом, время работы запущенного алгоритма для каждого — . Когда найдётся первое , то алгоритм успешно завершится..
Общее время работы алгоритма для всех обработанных значений — . Заметим, что , так как в противном случае , что противоречит тому, что — первый из тех, которые больше . Следовательно, .Получаем время работы .См. также
Источники информации |