Быстрый поиск наибольшей возрастающей подпоследовательности
| Задача: |
| Дана перестановка . Требуется найти НВП за , где — длина НВП. |
Содержание
Алгоритм O(n log log 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) // добавленный элемент — не максимальный // удаляем предыдущее значение — заменяем следующий 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 | 2 | 3 | 4 | 5 | 1 | 2 | 3 | 4 | 5 | 1 | 2 |
После сортировки:
| Блок | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 2 | 3 | 3 |
| 3 | 4 | 8 | 9 | 10 | 1 | 2 | 5 | 6 | 12 | 7 | 11 | |
| Смещение | 2 | 4 | 5 | 1 | 3 | 1 | 2 | 5 | 4 | 3 | 1 | 2 |
Обратные перестановки ():
| 1 | 2 | 3 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 4 | 1 | 5 | 2 | 3 | 1 | 2 | 5 | 4 | 3 | 1 | 2 | |
Обработка блока
Основная идея
- Достаем из очереди ключи и конвертируем их в элементы .
- Классический merge только что полученных элементов с элементами нового блока, но с модификацией: генерируются 2 дополнительных массива - индексы элементов исходных массивов в новом. Слияние массивов назовем .
- На массив индексов, относящиеся к новому блоку, действует перестановка смещений сортированного варианта этого блока. Таким образом мы добиваемся эквиваленции отношений ключей к отношениям элементов в очереди и элементов в блоке, а ключи находятся в диапазоне .
- Первый массив индексов, который соответствует элементам, ранее находящимся в очереди, вновь кладутся в очередь (обычными -ами). Второй массив обрабатывается описанным выше алгоритмом , при том массив строится из ключей с помощью .
Визуализация
Помеченные зеленым — условные данные. Остальное — условные операции. Например значит взять элементы из массива c индексами из . — здесь обозначает результат операции .
Для первого блока содержательной является лишь ветка, начинающаяся с , что не противоречит представленной схеме.
Пример
Первый блок
|
| ||||||||||||||||||||||||||||||||
аналогичен сортированному, т.к. предыдущих ключей нет.
| Ключи сортированного блока | 2 | 4 | 5 | 1 | 3 |
| 4 | 1 | 5 | 2 | 3 |
Пропускаем их через :
| 4 | 4 | ||
| 1 | 1 | ||
| 1 | 5 | 5 | |
| 1 | 2 | 2 | |
| 1 | 2 | 3 | 3 |
Результат работы
Второй блок
Восстанавливаем элементы из : .
|
| ||||||||||||||||||||||||||||||||
|
|
| |||||||||||||||||||||||||||||||||||
| Ключи сортированного блока | 1 | 2 | 5 | 4 | 3 |
| 1 | 2 | 5 | 4 | 3 |
Восстанавливаем порядок новых из и :
| новые ключи | ||||
|---|---|---|---|---|
| 1 | 2 | 8 | 6 | 5 |
Обновление старых ключей:
| 3 | 3 | ||
| 3 | 4 | 4 | |
| 3 | 4 | 7 | 7 |
новых:
| 1 | 4 | 7 | 1 | |
| 1 | 2 | 7 | 2 | |
| 1 | 2 | 7 | 8 | 8 |
| 1 | 2 | 6 | 8 | 6 |
| 1 | 2 | 5 | 8 | 5 |
Результат работы
Третий блок
Восстанавливаем элементы из : .
|
| ||||||||||||||||||||
|
|
| |||||||||||||||||||||||||||||
| Ключи сортированного блока | 1 | 2 |
| 1 | 2 |
Восстанавливаем порядок новых из и :
| новые ключи | |
|---|---|
| 4 | 5 |
Обновление старых ключей:
| 1 | 1 | |||
| 1 | 2 | 2 | ||
| 1 | 2 | 3 | 3 | |
| 1 | 2 | 3 | 6 | 6 |
новых:
| 1 | 2 | 3 | 4 | 4 | |
| 1 | 2 | 3 | 4 | 5 | 5 |
Результат работы
Восстановление НВП
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 1 | 3 | 2 | 2 | 5 | 4 | 3 | 7 | 8 | |||
Начинаем восстановление с :
| обратный порядок | 11 | 7 | 5 | 2 | 1 |
| НВП | 1 | 2 | 5 | 7 | 11 |
См. также
- Дерево ван Эмде Боаса
- Задача о наибольшей возрастающей подпоследовательности
- Задача о наибольшей общей подпоследовательности
- Наибольшая общая возрастающая подпоследовательность
- Задача о наибольшей общей палиндромной подпоследовательности




