Участник:Artem.ustinov/НВП — различия между версиями
(→Основная идея) |
(Добавлен новый раздел) |
||
Строка 188: | Строка 188: | ||
* Вставляем в <tex>B</tex> новые ключи элементов списка <tex>\mathtt{elems}</tex>. | * Вставляем в <tex>B</tex> новые ключи элементов списка <tex>\mathtt{elems}</tex>. | ||
* Обрабатываем ключи элементов блока в порядке исходной последовательности с помощью алгоритма <tex>\mathrm{LIS}</tex>. Для восстановления НВП также используем массив "предшественников", который будет работать с соответствующими ключами элементов <tex>\mathtt{elt}(x)</tex>. | * Обрабатываем ключи элементов блока в порядке исходной последовательности с помощью алгоритма <tex>\mathrm{LIS}</tex>. Для восстановления НВП также используем массив "предшественников", который будет работать с соответствующими ключами элементов <tex>\mathtt{elt}(x)</tex>. | ||
+ | |||
+ | ===Доказательство корректности алгоритма=== | ||
+ | |||
===Пример=== | ===Пример=== |
Версия 16:37, 31 декабря 2017
Задача: |
Дана перестановка НВП за , где — длина НВП. | множества . Требуется найти
Содержание
Алгоритм за O(n log log n)
Нахождение длины НВП
Основная идея
Пусть
— входная перестановка.Будем последовательно обрабатывать элементы в порядке
Для каждой длины
предполагаемой НВП находим наименьший элемент, который может быть последним в возрастающей подпоследовательности длины и запишем его в массив . Будем называть его наилучшим элементом для длины .- Если больше каждого элемента , вычисленного для подпоследовательности , тогда с ним можно сделать возрастающую подпоследовательность максимальной длины из уже рассмотренных, в которой он будет последним элементом. Значит, записываем его в конец .
- Иначе будет наилучшим элементом для уже существующей длины, тогда мы находим наименьшее и заменяем элементом .
Следует отметить, что полученный массив также образует возрастающую последовательность, на котором мы должны выполнять операции приоритетную очередь, реализованную через Дерево ван Эмде Боаса. Так как данная структура данных производит описанные операции за , где k — количество бит чисел, которые позволяет хранить дерево, то полученный алгоритм работает за , потому что все элементы последовательности не превосходят n.
, соответственно целесообразно использоватьПример
Типы операций
- Добавление элемента, который больше всех предыдущих:
- Замещение элемента более подходящим, т.е. добавление немаксимального элемента:
Пример последовательности
9 | 3 | 10 | 4 | 8 | 1 | 2 | 12 | 6 | 5 | 7 | 11 |
Состояние очереди при каждом добавлении
9 | 9 | ||||
3 | 3 | ||||
3 | 10 | 10 | |||
3 | 4 | 4 | |||
3 | 4 | 8 | 8 | ||
1 | 4 | 8 | 1 | ||
1 | 2 | 8 | 2 | ||
1 | 2 | 8 | 12 | 12 | |
1 | 2 | 6 | 12 | 6 | |
1 | 2 | 5 | 12 | 5 | |
1 | 2 | 5 | 7 | 7 | |
1 | 2 | 5 | 7 | 11 | 11 |
Псевдокод
int LIS(
[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
Расширение алгоритма до нахождения НВП
Основная идея
Будем запоминать пары: для каждого элемента записываем его "предшественника".
Тогда, пройдя по предшественникам, начиная с последнего элемента очереди
, мы можем восстановить НВП.Общий вид алгоритма
9 | 9 | ||||
3 | 3 | ||||
3 | 10 | 10 | |||
3 | 4 | 4 | |||
3 | 4 | 8 | 8 | ||
1 | 4 | 8 | 1 | ||
1 | 2 | 8 | 2 | ||
1 | 2 | 8 | 12 | 12 | |
1 | 2 | 6 | 12 | 6 | |
1 | 2 | 5 | 12 | 5 | |
1 | 2 | 5 | 7 | 7 | |
1 | 2 | 5 | 7 | 11 | 11 |
predecessor | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
1 | 3 | 2 | 2 | 5 | 4 | 3 | 7 | 8 |
Псевдокод
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)
Чтобы Дерево ван Эмде Боаса выполняло операции за , необходимо алфавит обрабатываемых значений уменьшить до .
Предположим, мы знаем такое приближение числа
числом . Мы обсудим, как найти такое позже.Чтобы достичь нужной оценки, будем делить последовательность на блоки длины
, кроме последнего, который может быть меньше, и обрабатывать каждый блок отдельно.Деление на блоки
Последовательность
делится на блоки элементов:Обозначим за
отсортированный блок . Отсортированные и неотсортированные блоки будем хранить в памяти.Цифровая сортировка каждых блоков отдельно будет давать нам время рваботы . Чтобы отсортировать их за линейное время, дополним каждый элемент номером его блока и получим пары . Цифровая сортировка этих пар, если принимать за старший разряд номер блока, а за младший значение элемента, будет работать за , потому что значения элементов и номера блоков не превосходят .
Обработка блока
Обрабатывая блоки, будем работать не со значениями элементов, а с ключами, которые определенны для каждого элемента внутри блоков. Все блоки будут обрабатываться онлайн, то есть мы не перейдём к обработке следующего блока, пока не закончим с текущим.
Каждому элементу
взаимно однозначно сопоставим ключ . Все значения ключей расположим в промежутке , и в очереди будем работать со значениями ключей элементов.Чтобы определить ключи элементов так, чтобы их значения были в представленном промежутке, будем, работая с блоком слияния. Поскольку мы предположили, что , то количество ключей в не больше , тогда длина не больше , что позволяет однозначно определить ключи на множестве .
, сливать элементы, ключи которых находятся в очереди , с в список . Получим ключи, сопоставив каждому элементу в его позицию в этом списке. Как было замечено ранее, элементы, чьи ключи находятся в располагаются в возрастающем порядке, поэтому возможно производить тривиальную операциюПосле того, как мы определили новые ключи для элементов, обновим ключи в очереди
.Затем запускаем описанный выше алгоритм
, для ключей элементов в порядке исходной последовательности.В итоге, обработка блока делится на следующие этапы:
- Достаем из очереди ключи , конвертируем их в элементы и кладём в список .
- Сливаем элементы в со следующим отсортированным блоком в список .
- Присваеваем новые ключи элементам в порядке списка .
- Вставляем в новые ключи элементов списка .
- Обрабатываем ключи элементов блока в порядке исходной последовательности с помощью алгоритма . Для восстановления НВП также используем массив "предшественников", который будет работать с соответствующими ключами элементов .
Доказательство корректности алгоритма
Пример
Предположим, что
. Исходно получаем:Блок | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 2 | 3 | 3 |
9 | 3 | 10 | 4 | 8 | 1 | 2 | 12 | 6 | 5 | 7 | 11 |
После сортировки:
Блок | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 2 | 3 | 3 |
3 | 4 | 8 | 9 | 10 | 1 | 2 | 5 | 6 | 12 | 7 | 11 |
Первый блок
|
|
Обработка блока с помощью алгоритма
.4 | 4 | 9 | ||
1 | 1 | 3 | ||
1 | 5 | 5 | 10 | |
1 | 2 | 2 | 4 | |
1 | 2 | 3 | 3 | 8 |
В результате получаем
Второй блок
Восстанавливаем элементы
из : .Сливаем
и восстановленные элементы из :
|
|
|
|
|
Обновляем ключи в очереди:
3 | 3 | ||
3 | 4 | 4 | |
3 | 4 | 7 | 7 |
запускаем
для блока:1 | 4 | 7 | 1 | 1 | |
1 | 2 | 7 | 2 | 2 | |
1 | 2 | 7 | 8 | 8 | 12 |
1 | 2 | 6 | 8 | 6 | 6 |
1 | 2 | 5 | 8 | 5 | 5 |
В результате получаем:
Третий блок
Восстанавливаем элементы
из : .Сливаем
и восстановленные элементы из :
|
|
|
Обновление старых ключей:
запускаем для блока:
Результат завершения алгоритма:
Получаем, что длина НВП — , и НВП оканчивается на .
Начинаем восстановление с :
Оценка времени работыТак как размер списка не больше , а количество блоков всего . То общее количество присваиваний новых ключей элементам последовательности, также как и количество операций слияния списков, не больше , где c — некоторая константа. Каждая операция с приоритетной очередью требует времени, так как элементы в не больше .Рассмотрим последовательность , где , — некоторое значение, меньшее .Будем по порядку для элементов этой последовательности запускать алгоритм, представленный выше. Если размер очереди становится больше , то условие перестает выполняться, тогда останавливаем алгоритм и переходим к следующему значению . Когда найдётся первое , то алгоритм успешно завершится.Таким образом, время работы запущенного алгоритма — для .Заметим, что .
Общее время работы алгоритма по всем — .Обратим внимание, что , так как в противном случае , что противоречит тому, что — первый из тех, которые больше . Следовательно, .Тогда алгоритм также работает за время .См. также
Источники информации |