Декомпозиция Линдона
Содержание
Основные определения
Декомпозиция Линдона была изобретена Роджером Линдоном (англ. Roger Lyndon) в 1954 году. Она используется для нахождения лексикографически минимального и максимального суффиксов строки.
Определение: |
Простая строка — строка, которая строго лексикографически меньше любого своего суффикса. |
Определение: |
Декомпозиция Линдона (англ. Lyndon decomposition) строки | — её разложение , где строки просты, и при этом .
Существование и единственность
Лемма: |
1. 2. — простая |
Доказательство: |
1. Так как , и , 2. Пусть — суффикс строки1) 2) 3) — суффикс . Так как — простая, , . Так как — простая, и |
Теорема: |
Можно построить декомпозицию Линдона любой строки , причем единственным образом. |
Доказательство: |
1. Существование. Разобьем строку на символы. Будем их склеивать, если подряд идущие символы: . Так как символ — простая строка, по лемме — тоже простая и . Далее склеиваем строки, не удовлетворяющие условию . Это конечный процесс, так как длина строки конечна получим нужное разбиение.Пусть существует хотя бы одно разбиение строки на простые слова. Возьмем разбиение строки на простые слова (без условия ) такое, чтобы было минимально. Пусть в нем есть , тогда эти строки можно сконкатернировать получим разбиение с меньшим числом слов — противоречие с выбором .Получили: — минимально нет2. Единственность. Пусть существует несколько разбиений , удовлетворяющих условию теоремы. Сравним длины первых двух слов и , если , сравним вторые и так далее. Если у всех слов длины одинаковы, то разбиения совпадают — противоречие. Иначе Покажем, что такого не может быть:1) Пусть Тогда , где — префикс , Получаем: (так как простая и по определению меньше своего суффикса), (так как — префикс), (по условию разбиения), (их начало совпадает, и по предположению. Получили противоречие: .2) Пусть То есть не может быть строк — проверяется аналогично. несовпадающей длины разбиения равны. |
Алгоритм Дюваля
Алгоритм
Алгоритм Дюваля (англ. Duval's algorithm) находит для данной строки длины
декомпозицию Линдона за время с использованием дополнительной памяти. Относится к классу жадных алгоритмов.
Определение: |
Предпростая строка — строка | , такая что , где — некоторая простая строка, а - некоторый префикс строки .
Во время работы алгоритма строка s разделена на три строки s = s_1 + s_2 + s_3, где в строке s_1 декомпозиция Линдона уже найдена и s_1 уже больше не используется алгоритмом; строка s_2 — это предпростая строка (причём длину простых строк внутри неё мы также запоминаем); строка s_3 — это ещё не обработанная часть строки s. Алгоритм Дюваля берёт первый символ строки s_3 и пытается дописать его к строке s_2. При этом, возможно, для какого-то префикса строки s_2 декомпозиция Линдона становится известной, и эта часть переходит к строке s_1.
Будем поддерживаться указатель i на начало строки s_2. Внешний цикл алгоритма будет выполняться, пока i < n, то есть пока вся строка s не перейдёт в строку s_1. Внутри этого цикла создаются два указателя: указатель j на начало строки s_3 и указатель k на текущий символ в строке s_2, с которым будет производиться сравнение. Затем будем в цикле пытаться добавить символ s[j] к строке s_2, для чего необходимо произвести сравнение с символом s[k]. Здесь у нас возникают три различных случая:
1. Если s[j] = s[k], то мы можем дописать символ s[j] к строке s_2, не нарушив её "предпростоты". Следовательно, в этом случае мы просто увеличиваем указатели j и k на единицу.
2. Если s[j] > s[k], то, очевидно, строка s_2 + s[j] станет простой. Тогда мы увеличиваем j на единицу, а k передвигаем обратно на i, чтобы следующий символ сравнивался с первым символом s_2.
3. Если s[j] < s[k], то строка s_2 + s[j] уже не может быть предпростой. Поэтому мы разбиваем предпростую строку s_2 на простые строки плюс "остаток" (префикс простой строки, возможно, пустой); простые строки добавляем в ответ (т.е. выводим их позиции, попутно передвигая указатель i), а "остаток" вместе с символом s[j] переводим обратно в строку s_3, и останавливаем выполнение внутреннего цикла. Тем самым мы на следующей итерации внешнего цикла заново обработаем остаток, зная, что он не мог образовать предпростую строку с предыдущими простыми строками. Осталось только заметить, что при выводе позиций простых строк нам нужно знать их длину; но она, очевидно, равна j - k.
Реализация
string s // входная строка string[] words // декомпозиция n|s| i 0 w 0 while (i < n) { j i + 1 k i while (j < n and s[k] <= s[j]) { if s[k] < s[j] k i else k k + 1 j j + 1 } while (i <= k) { words[w] s[i..j-k] w w + 1 i i + j - k; } }
Асимптотика
Дополнительная память требуется только на три указателя: i, j, k.
Внешний цикл while делает не более n итераций, поскольку в конце каждой его итерации к результату добавляется как минимум один символ (а всего символов n).
Оценим теперь количество итераций первого вложенного цикла while. Для этого рассмотрим второй вложенный цикл while — он при каждом своём запуске выводит некоторое количество r >= 1 копий одной и той же простой строки некоторой длины p = j - k. Заметим, что строка s_2 является предпростой, причём её простые строки имеют длину как раз p, т.е. её длина не превосходит r * p + p - 1. Поскольку длина строки s_2 равна j - i, а указатель j увеличивается по единице на каждой итерации первого вложенного цикла while, то этот цикл выполнит не более r* p + p - 2 итераций. Худшим случаем является случай r = 1, и мы получаем, что первый вложенный цикл while всякий раз выполняет не более 2p - 2 итераций. Вспоминая, что всего выводится n символов, получаем, что для вывода n символов требуется не более 2n - 2 итераций первого вложенного while. Следовательно, алгоритм Дюваля выполняется за O (n).
Легко оценить и число сравнений символов, выполняемых алгоритмом Дюваля. Поскольку каждая итерация первого вложенного цикла while производит два сравнения символов, а также одно сравнение производится после последней итерации цикла, то общее число сравнений символов не превосходит 4n - 3.