Быстрый поиск наибольшей возрастающей подпоследовательности — различия между версиями
| Shersh (обсуждение | вклад) м (→Псевдокод) | м (Снята с разработки) | ||
| Строка 1: | Строка 1: | ||
| − | |||
| − | |||
| {{Задача | {{Задача | ||
| |definition = Дана {{Acronym | перестановка | на самом деле может быть и мультиперестановкой }} <tex>\pi</tex> <tex>\{1, 2,~\dots,~n\}</tex>. Требуется найти [[Задача о наибольшей возрастающей подпоследовательности | НВП]] <tex>\pi</tex> за <tex>O(n\operatorname{log}\operatorname{log}k)</tex>, где <tex>k</tex> — длина НВП. | |definition = Дана {{Acronym | перестановка | на самом деле может быть и мультиперестановкой }} <tex>\pi</tex> <tex>\{1, 2,~\dots,~n\}</tex>. Требуется найти [[Задача о наибольшей возрастающей подпоследовательности | НВП]] <tex>\pi</tex> за <tex>O(n\operatorname{log}\operatorname{log}k)</tex>, где <tex>k</tex> — длина НВП. | ||
| Строка 136: | Строка 134: | ||
| ==== Псевдокод ==== | ==== Псевдокод ==== | ||
| <code> | <code> | ||
| − |      ''' | + |      '''int[]''' LIS(<tex>\pi</tex>[n]) | 
|          '''PriorityQueue''' B |          '''PriorityQueue''' B | ||
|          '''int''' k = 0 |          '''int''' k = 0 | ||
| Строка 181: | Строка 179: | ||
| Разделим исходную перестановку <tex>\pi</tex> на блоки <tex>C_j = \{\pi_{(j-1)m + 1},~\pi_{(j-1)m + 2},~\dots, ~\pi_{(j-1)m + m}\}</tex>. | Разделим исходную перестановку <tex>\pi</tex> на блоки <tex>C_j = \{\pi_{(j-1)m + 1},~\pi_{(j-1)m + 2},~\dots, ~\pi_{(j-1)m + m}\}</tex>. | ||
| − | Получим сортированные варианты этих блоков <tex>C_j^S</tex>. Лобовая [[цифровая сортировка]] дает нам время работы <tex>O(\dfrac{n}{m}n)</tex>. Дополним каждый элемент <tex>\pi</tex> номером блока, в котором он находится и смещением в этом блоке. Теперь, {{Acronym|рассматривая номер блока как старший разряд, элемент как младший разряд|по смещению внутри блока не сортируем}}, можно сортировать цифровой сортировкой за линейное время <tex>O(n)</tex>. | + | Получим сортированные варианты этих блоков <tex>C_j^S</tex>. Лобовая [[цифровая сортировка]] дает нам время работы <tex>O \left(\dfrac{n}{m}n \right) = O \left(\dfrac{n^2}{m} \right)</tex>. Дополним каждый элемент <tex>\pi</tex> номером блока, в котором он находится и смещением в этом блоке. Теперь, {{Acronym|рассматривая номер блока как старший разряд, элемент как младший разряд|по смещению внутри блока не сортируем}}, можно сортировать цифровой сортировкой за линейное время <tex>O(n)</tex>. | 
| Перестановка смещений, образованная в сортированном блоке есть не что иное, как обратная перестановка перестановки, элементы которой соотносятся между собой как элементы исходного блока. Находим обратную перестановку к найденной, назовем ее <tex>\xi</tex>. | Перестановка смещений, образованная в сортированном блоке есть не что иное, как обратная перестановка перестановки, элементы которой соотносятся между собой как элементы исходного блока. Находим обратную перестановку к найденной, назовем ее <tex>\xi</tex>. | ||
| Строка 479: | Строка 477: | ||
| [[Категория: Дискретная математика и алгоритмы]] | [[Категория: Дискретная математика и алгоритмы]] | ||
| [[Категория: Динамическое программирование]] | [[Категория: Динамическое программирование]] | ||
| + | [[Категория: Классические задачи динамического программирования]] | ||
Версия 00:37, 8 января 2017
| Задача: | 
| Дана перестановка . Требуется найти НВП за , где — длина НВП. | 
Содержание
Алгоритм 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 | 4 | 7 | 1 | |
| 1 | 2 | 7 | 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 | 
См. также
- Дерево ван Эмде Боаса
- Задача о наибольшей возрастающей подпоследовательности
- Задача о наибольшей общей подпоследовательности
- Наибольшая общая возрастающая подпоследовательность
- Задача о наибольшей общей палиндромной подпоследовательности





