Декомпозиция Линдона — различия между версиями
Shersh (обсуждение | вклад) (→Существование и единственность) |
Shersh (обсуждение | вклад) (→Алгоритм Дюваля: переписан алгоритм) |
||
Строка 80: | Строка 80: | ||
}} | }} | ||
− | Во время работы алгоритма строка <tex>s</tex> | + | Во время работы алгоритма строка <tex>s</tex> представляется в виде конкатенации трёх строк <tex>s = s_1s_2s_3</tex>, где для строки <tex>s_1</tex> декомпозиция Линдона уже найдена, и <tex>s_1</tex> уже больше не используется алгоритмом; строка <tex>s_2</tex> {{---}} это предпростая строка; строка <tex>s_3</tex> {{---}} ещё не обработанная алгоритмом часть строки <tex>s</tex>. Алгоритм Дюваля берёт первый символ строки <tex>s_3</tex> и пытается дописать его к строке <tex>s_2</tex>. При этом, возможно, для какого-то префикса строки <tex>s_2</tex> декомпозиция Линдона становится известной, и эта часть переходит к строке <tex>s_1</tex>. |
− | Будем | + | Будем поддерживать три указателя: |
+ | * <tex>i</tex> {{---}} на начало строки <tex>s_2</tex> | ||
+ | * <tex>j</tex> {{---}} на текущий символ в строке <tex>s_2</tex>, с которым будет производиться сравнение | ||
+ | * <tex>k</tex> {{---}} на начало строки <tex>s_3</tex> | ||
+ | |||
+ | Внешний цикл алгоритма будет выполняться, пока <tex>i < n</tex>, то есть пока вся строка <tex>s</tex> не перейдёт в строку <tex>s_1</tex>. Внутри этого цикла создаются два указателя <tex> j </tex> и <tex> k </tex>. Затем будем пытаться добавить символ <tex>s[k]</tex> к строке <tex>s_2</tex>, для чего необходимо произвести сравнение с символом <tex>s[j]</tex>. При этом будем поддерживать инвариант: <tex>k - j</tex> {{---}} длина подстроки <tex> w </tex>. | ||
Возникают три различных случая: | Возникают три различных случая: | ||
− | + | * <tex>s[j] = s[k]:</tex> тогда дописывыем символ <tex>s[k]</tex> к строке <tex>s_2</tex>. | |
− | + | * <tex>s[j] < s[k]:</tex> тогда строка <tex>s_2 + s[k]</tex> станет простой. Значит, мы увеличим <tex>k</tex> на единицу, а <tex>j</tex> передвигаем обратно на <tex>i</tex>, чтобы следующий символ сравнивался с первым символом <tex>s_2</tex>. То есть получаем новую простую строку длины <tex>k - j</tex>. | |
− | + | * <tex>s[j] > s[k]:</tex> значит, строка <tex>s_2 + s[k]</tex> уже не может быть предпростой. Добавляем к <tex> s_1 </tex> все строки <tex> w </tex>, а по нашему инварианту мы знаем, что их длина равна <tex> k - j </tex>, затем сдвигаем <tex> i </tex> к началу позиции строки <tex> w' </tex>. После чего внешний цикл запускаем заново: | |
− | |||
− | |||
===Реализация=== | ===Реализация=== | ||
− | + | '''function''' lyndon('''string''' s, '''string[]''' decomposition): | |
− | |||
n <tex>\leftarrow</tex> |s| | n <tex>\leftarrow</tex> |s| | ||
i <tex>\leftarrow</tex> 0 | i <tex>\leftarrow</tex> 0 | ||
cur <tex>\leftarrow</tex> 0 | cur <tex>\leftarrow</tex> 0 | ||
'''while''' i <tex> < </tex> n: | '''while''' i <tex> < </tex> n: | ||
− | j <tex>\leftarrow</tex> i | + | j <tex>\leftarrow</tex> i |
− | k <tex>\leftarrow</tex> i | + | k <tex>\leftarrow</tex> i + 1 |
− | '''while''' | + | '''while''' k <tex> < </tex> n '''and''' s[j] <tex> \leqslant </tex> s[k]: |
− | '''if''' s[ | + | '''if''' s[j] <tex> < </tex> s[k]: |
− | + | j <tex>\leftarrow</tex> i | |
'''else:''' | '''else:''' | ||
− | + | j <tex>\leftarrow</tex> k + 1 | |
− | + | k <tex>\leftarrow</tex> k + 1 | |
− | '''while''' i <tex>\leqslant</tex> | + | '''while''' i <tex>\leqslant</tex> j: |
− | + | decomposition[cur] <tex>\leftarrow</tex> s[i..k - j] | |
− | cur <tex>\leftarrow</tex> | + | cur <tex>\leftarrow</tex> cur + 1 |
− | i <tex>\leftarrow</tex> i + j | + | i <tex>\leftarrow</tex> i + k - j |
===Корректность=== | ===Корректность=== |
Версия 20:52, 4 мая 2014
Декомпозиция Линдона была изобретена Роджером Линдоном (англ. Roger Lyndon) в 1954 году. Она используется для нахождения лексикографически минимального и максимального суффиксов строки, а также лексикографически минимального циклического сдвига.
Содержание
Основные определения
Определение: |
Простая строка — строка, которая лексикографически меньше любого своего суффикса. |
Примеры:
— простая строка, так как , , , .
— не простая строка, так как .
Определение: |
Декомпозиция Линдона (англ. Lyndon decomposition) строки | — её разложение , где строки просты, и при этом .
Существование и единственность
Лемма: |
1. 2. — простая |
Доказательство: |
1. Так как , то и ,2. Пусть — суффикс строки . Тогда рассмотрим 3 возможных случая:
|
Теорема (Чен-Линдон-Фокс): |
Можно построить декомпозицию Линдона любой строки , причем единственным образом. |
Доказательство: |
1. Существование. У каждой строки существует хотя бы одно разбиение на простые слова. Это следует из того, что отдельный символ является простым словом. Тогда среди всех разбиений строки на простые слова возьмём то, в котором меньше всего слов. Покажем, что это и будет декомпозицией Линдона данной строки. Предположим, что это не так. Значит, леммы следует, что эти слова можно сконкатенировать и получить разбиение строки на меньшее число слов. Получили противоречие. . Так как слова и простые, то из доказаннойТаким образом доказали даже более сильное утверждение: , — минимально нет2. Единственность. Пусть существует несколько разбиений , удовлетворяющих условию теоремы. Сравним длины первых двух слов и , если , сравним вторые и так далее. Если у всех слов длины одинаковы, то разбиения совпадают — противоречие. Иначе .Покажем, что такого не может быть: 1) Пусть , тогда , где — префикс , . Тогда получаем:
Пришли к противоречию: .2) Случай То есть не может быть строк симметричен разобранному. несовпадающей длины, значит, разбиения равны. |
Алгоритм Дюваля
Алгоритм
Алгоритм Дюваля (англ. Duval's algorithm) находит для данной строки длины
декомпозицию Линдона за время с использованием дополнительной памяти. Он строит декомпозицию только на упорядоченных алфавитах.
Определение: |
Предпростая строка — строка | , такая что , где — некоторая простая строка, а — некоторый префикс строки .
Во время работы алгоритма строка представляется в виде конкатенации трёх строк , где для строки декомпозиция Линдона уже найдена, и уже больше не используется алгоритмом; строка — это предпростая строка; строка — ещё не обработанная алгоритмом часть строки . Алгоритм Дюваля берёт первый символ строки и пытается дописать его к строке . При этом, возможно, для какого-то префикса строки декомпозиция Линдона становится известной, и эта часть переходит к строке .
Будем поддерживать три указателя:
- — на начало строки
- — на текущий символ в строке , с которым будет производиться сравнение
- — на начало строки
Внешний цикл алгоритма будет выполняться, пока
, то есть пока вся строка не перейдёт в строку . Внутри этого цикла создаются два указателя и . Затем будем пытаться добавить символ к строке , для чего необходимо произвести сравнение с символом . При этом будем поддерживать инвариант: — длина подстроки .Возникают три различных случая:
- тогда дописывыем символ к строке .
- тогда строка станет простой. Значит, мы увеличим на единицу, а передвигаем обратно на , чтобы следующий символ сравнивался с первым символом . То есть получаем новую простую строку длины .
- значит, строка уже не может быть предпростой. Добавляем к все строки , а по нашему инварианту мы знаем, что их длина равна , затем сдвигаем к началу позиции строки . После чего внешний цикл запускаем заново:
Реализация
function lyndon(string s, string[] decomposition): n|s| i 0 cur 0 while i n: j i k i + 1 while k n and s[j] s[k]: if s[j] s[k]: j i else: j k + 1 k k + 1 while i j: decomposition[cur] s[i..k - j] cur cur + 1 i i + k - j
Корректность
Покажем, что алгоритм получает нужное разложение. То есть все
- простые и лексикографически.При обработке текущего символа в первом случае просто сдвигаем указатели, не записывая ответ. Во втором случае объединяем все найденные
с и получем новую строку .Покажем, что
является простой. Рассмотрим ее суффикс. Если он начинается в середине , сравним его посимвольно со строкой , и тогда в каком-то символе он окажется больше , так как суффикс начинается с , а строка — простая и по определению меньше всех своих суффиксов. Если суффикс начинается в , то при сравнении расхождение будет в символах и . Но , так что суффикс меньше . Если же суффикс начинается в начале , отбросим общий префикс вида и придем к предыдущему случаю.В третьем случае просто выведем все
и продолжим обработку со строки , так как при добавлении перестанет удовлетворять требованиям, ведь в этом случае суффикс будет меньше .Теперь покажем, что
.Последоваельность из
будет удовлетворять условию, так как эти строки равны. Следующее слово будет иметь общий префикс с , а после него будет стоять символ, меньший следующего символа из (новое получается по третьему случаю).Асимптотика
Дополнительная память требуется только на три указателя:
.Внешний цикл
делает не более итераций, поскольку в конце каждой его итерации к результату добавляется как минимум один символ (а всего символов ).Оценим теперь количество итераций первого вложенного цикла
. Для этого рассмотрим второй вложенный цикл — он при каждом своём запуске выводит некоторое количество копий одной и той же простой строки некоторой длины . Заметим, что строка является предпростой, причём её простые строки имеют длину как раз , т.е. её длина не превосходит . Поскольку длина строки равна , а указатель увеличивается по единице на каждой итерации первого вложенного цикла , то этот цикл выполнит не более итераций. Худшим случаем является случай , и мы получаем, что первый вложенный цикл всякий раз выполняет не более итераций. Вспоминая, что всего выводится символов, получаем, что для вывода символов требуется не более итераций первого вложенного . Следовательно, алгоритм Дюваля выполняется за .Легко оценить и число сравнений символов, выполняемых алгоритмом Дюваля. Поскольку каждая итерация первого вложенного цикла
производит два сравнения символов, а также одно сравнение производится после последней итерации цикла, то общее число сравнений символов не превосходит .