Декомпозиция Линдона — различия между версиями
(→Алгоритм Дюваля) |
|||
Строка 1: | Строка 1: | ||
+ | Декомпозиция Линдона была изобретена Роджером Линдоном (англ. ''Roger Lyndon'') в 1954 году. Она используется для нахождения лексикографически минимального и максимального суффиксов строки. | ||
+ | |||
==Основные определения== | ==Основные определения== | ||
− | |||
{{Определение | {{Определение | ||
|id=def1. | |id=def1. | ||
− | |definition='''Простая строка''' {{---}} строка, которая | + | |definition='''Простая строка''' {{---}} строка, которая лексикографически меньше любого своего суффикса. |
}} | }} | ||
+ | |||
+ | Примеры: | ||
+ | |||
+ | <tex>ababb</tex> {{---}} простая строка, так как <tex>ababb < babb</tex>, <tex>ababb < abb</tex>, <tex>ababb < bb</tex>, <tex>ababb < b</tex>. | ||
+ | |||
+ | <tex>babaa</tex> {{---}} не простая строка, так как <tex>babaa > aa</tex>. | ||
{{Определение | {{Определение | ||
|id=def2. | |id=def2. | ||
− | |definition='''Декомпозиция Линдона''' (англ. ''Lyndon decomposition'') строки <tex>s</tex> {{---}} её разложение <tex>s = | + | |definition='''Декомпозиция Линдона''' (англ. ''Lyndon decomposition'') строки <tex>s</tex> {{---}} её разложение <tex>s = s_1s_2...s_k</tex>, где строки <tex>s_i</tex> просты, и при этом <tex>s_1 \geqslant s_2 \geqslant ... \geqslant s_k</tex>. |
}} | }} | ||
Строка 15: | Строка 22: | ||
{{Лемма | {{Лемма | ||
|id=lemma | |id=lemma | ||
− | |statement=<tex>s </tex>, <tex>t</tex> {{---}} простые и <tex>s < t</tex> лексикографически. Тогда: | + | |statement=<tex>s </tex>, <tex>t</tex> {{---}} простые и <tex>s < t</tex> лексикографически. Тогда верны следующие утверждения: |
+ | |||
1. <tex>s + t < t</tex> | 1. <tex>s + t < t</tex> | ||
+ | |||
2. <tex>s + t</tex> {{---}} простая | 2. <tex>s + t</tex> {{---}} простая | ||
|proof= | |proof= | ||
1. Так как <tex>s < t</tex>, <tex>\mathcal {9} i : s[i] < t[i]</tex> и <tex>s[j] = t[j]</tex>, <tex>j < i \rightarrow s + t < t</tex> | 1. Так как <tex>s < t</tex>, <tex>\mathcal {9} i : s[i] < t[i]</tex> и <tex>s[j] = t[j]</tex>, <tex>j < i \rightarrow s + t < t</tex> | ||
− | |||
− | |||
− | 1) <tex>|u| = |t| \rightarrow u = t \rightarrow u > s + t</tex> | + | 2. Пусть <tex>u</tex> {{---}} суффикс строки <tex>s + t</tex> |
+ | |||
+ | 1) <tex>|u| = |t| \rightarrow u = t \rightarrow u > s + t</tex> по пункту 1. | ||
− | 2) <tex>|u| < |t| \rightarrow u</tex> {{---}} суффикс <tex>t</tex>. Так как <tex>t</tex> {{---}} простая, <tex>t < u \rightarrow s + t < t < u</tex> | + | 2) <tex>|u| < |t| \rightarrow u</tex> {{---}} суффикс <tex>t</tex>. Так как <tex>t</tex> {{---}} простая, <tex>t < u по определению \rightarrow s + t < t < u</tex> |
3) <tex>|u| > |t| \rightarrow s = s' + s''</tex>, <tex>u = s'' + t</tex>. Так как <tex>s</tex> {{---}} простая, <tex>s < s''</tex> и <tex>|s''| < |s| \rightarrow s + t < s'' + t</tex> | 3) <tex>|u| > |t| \rightarrow s = s' + s''</tex>, <tex>u = s'' + t</tex>. Так как <tex>s</tex> {{---}} простая, <tex>s < s''</tex> и <tex>|s''| < |s| \rightarrow s + t < s'' + t</tex> | ||
Строка 44: | Строка 53: | ||
Пусть в нем есть <tex>s_i < s_{i+1}</tex>, тогда эти строки можно сконкатернировать <tex>\rightarrow</tex> получим разбиение с меньшим числом слов {{---}} противоречие с выбором <tex>k</tex>. | Пусть в нем есть <tex>s_i < s_{i+1}</tex>, тогда эти строки можно сконкатернировать <tex>\rightarrow</tex> получим разбиение с меньшим числом слов {{---}} противоречие с выбором <tex>k</tex>. | ||
− | Получили: <tex>k</tex> {{---}} минимально <tex>\ | + | Получили: <tex>k</tex> {{---}} минимально <tex>\Leftrightarrow</tex> нет <tex>s_i < s_{i+1}</tex> |
2. Единственность. | 2. Единственность. | ||
− | Пусть существует несколько разбиений <tex>s = | + | Пусть существует несколько разбиений <tex>s = s_1s_2...s_k = s_1's_2'...s_k'</tex>, |
удовлетворяющих условию теоремы. | удовлетворяющих условию теоремы. | ||
Сравним длины первых двух слов <tex>s_1</tex> и <tex>s_1'</tex>, если <tex>|s_1| = |s_1'|</tex>, сравним вторые и так далее. | Сравним длины первых двух слов <tex>s_1</tex> и <tex>s_1'</tex>, если <tex>|s_1| = |s_1'|</tex>, сравним вторые и так далее. | ||
Строка 56: | Строка 65: | ||
1) Пусть <tex>|s_i| > |s_i'|</tex> | 1) Пусть <tex>|s_i| > |s_i'|</tex> | ||
− | Тогда <tex>s_i = s_i' | + | Тогда <tex>s_i = s_i's_{i+1}'...t</tex>, где <tex>t</tex> {{---}} префикс <tex>s_{j+1}'</tex>, <tex>i \leqslant j</tex> |
Получаем: <tex>s_i < t</tex> (так как <tex>s_i</tex> простая и по определению меньше своего суффикса), | Получаем: <tex>s_i < t</tex> (так как <tex>s_i</tex> простая и по определению меньше своего суффикса), | ||
<tex>t < s_{j+1}'</tex> (так как <tex>t</tex> {{---}} префикс), | <tex>t < s_{j+1}'</tex> (так как <tex>t</tex> {{---}} префикс), | ||
Строка 69: | Строка 78: | ||
==Алгоритм Дюваля== | ==Алгоритм Дюваля== | ||
===Алгоритм=== | ===Алгоритм=== | ||
− | Алгоритм Дюваля (англ. ''Duval's algorithm'') находит для данной строки длины <tex>n</tex> декомпозицию Линдона за время <tex>O(n)</tex> с использованием <tex>O(1)</tex> дополнительной памяти. | + | Алгоритм Дюваля (англ. ''Duval's algorithm'') находит для данной строки длины <tex>n</tex> декомпозицию Линдона за время <tex>O(n)</tex> с использованием <tex>O(1)</tex> дополнительной памяти. Он строит декомпозицию только на упорядоченных алфавитах. Алгоритм Дюваля относится к классу жадных алгоритмов. |
{{Определение | {{Определение | ||
|id=def3 | |id=def3 | ||
− | |definition='''Предпростая строка''' {{---}} строка <tex>s</tex>, такая что <tex>s = | + | |definition='''Предпростая строка''' {{---}} строка <tex>s</tex>, такая что <tex>s = ww...ww'</tex>, где <tex>w</tex> {{---}} некоторая простая строка, а <tex>w'</tex> {{---}} некоторый префикс строки <tex>w</tex>. |
}} | }} | ||
− | Во время работы алгоритма строка <tex>s</tex> разделена на три строки <tex>s = | + | Во время работы алгоритма строка <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>i < n</tex>, то есть пока вся строка <tex>s</tex> не перейдёт в строку <tex>s_1</tex>. Внутри этого цикла создаются два указателя: указатель <tex>j</tex> на начало строки <tex>s_3</tex> и указатель <tex>k</tex> на текущий символ в строке <tex>s_2</tex>, с которым будет производиться сравнение. Затем будем в цикле пытаться добавить символ <tex>s[j]</tex> к строке <tex>s_2</tex>, для чего необходимо произвести сравнение с символом <tex>s[k]</tex>. Здесь у нас возникают три различных случая: | Будем поддерживаться указатель <tex>i</tex> на начало строки <tex>s_2</tex>. Внешний цикл алгоритма будет выполняться, пока <tex>i < n</tex>, то есть пока вся строка <tex>s</tex> не перейдёт в строку <tex>s_1</tex>. Внутри этого цикла создаются два указателя: указатель <tex>j</tex> на начало строки <tex>s_3</tex> и указатель <tex>k</tex> на текущий символ в строке <tex>s_2</tex>, с которым будет производиться сравнение. Затем будем в цикле пытаться добавить символ <tex>s[j]</tex> к строке <tex>s_2</tex>, для чего необходимо произвести сравнение с символом <tex>s[k]</tex>. Здесь у нас возникают три различных случая: | ||
Строка 92: | Строка 101: | ||
i <tex>\leftarrow</tex> 0 | i <tex>\leftarrow</tex> 0 | ||
w <tex>\leftarrow</tex> 0 | w <tex>\leftarrow</tex> 0 | ||
− | '''while''' | + | '''while''' i < n { |
j <tex>\leftarrow</tex> i + 1 | j <tex>\leftarrow</tex> i + 1 | ||
k <tex>\leftarrow</tex> i | k <tex>\leftarrow</tex> i | ||
− | '''while''' | + | '''while''' j < n '''and''' s[k] <= s[j] { |
'''if''' s[k] < s[j] | '''if''' s[k] < s[j] | ||
k <tex>\leftarrow</tex> i | k <tex>\leftarrow</tex> i | ||
Строка 102: | Строка 111: | ||
j <tex>\leftarrow</tex> j + 1 | j <tex>\leftarrow</tex> j + 1 | ||
} | } | ||
− | '''while''' | + | '''while''' i <tex>\leqslant</tex> k { |
words[w] <tex>\leftarrow</tex> s[i..j-k] | words[w] <tex>\leftarrow</tex> s[i..j-k] | ||
w <tex>\leftarrow</tex> w + 1 | w <tex>\leftarrow</tex> w + 1 | ||
Строка 115: | Строка 124: | ||
Дополнительная память требуется только на три указателя: <tex>i, j, k</tex>. | Дополнительная память требуется только на три указателя: <tex>i, j, k</tex>. | ||
− | Внешний цикл while делает не более <tex>n</tex> итераций, поскольку в конце каждой его итерации к результату добавляется как минимум один символ (а всего символов <tex>n</tex>). | + | Внешний цикл <tex>while</tex> делает не более <tex>n</tex> итераций, поскольку в конце каждой его итерации к результату добавляется как минимум один символ (а всего символов <tex>n</tex>). |
− | Оценим теперь количество итераций первого вложенного цикла while. Для этого рассмотрим второй вложенный цикл while {{---}} он при каждом своём запуске выводит некоторое количество <tex>r \geqslant 1</tex> копий одной и той же простой строки некоторой длины <tex>p = j - k</tex>. Заметим, что строка <tex>s_2</tex> является предпростой, причём её простые строки имеют длину как раз <tex>p</tex>, т.е. её длина не превосходит <tex>r | + | Оценим теперь количество итераций первого вложенного цикла <tex>while</tex>. Для этого рассмотрим второй вложенный цикл <tex>while</tex> {{---}} он при каждом своём запуске выводит некоторое количество <tex>r \geqslant 1</tex> копий одной и той же простой строки некоторой длины <tex>p = j - k</tex>. Заметим, что строка <tex>s_2</tex> является предпростой, причём её простые строки имеют длину как раз <tex>p</tex>, т.е. её длина не превосходит <tex>r \cdot p + p - 1</tex>. Поскольку длина строки <tex>s_2</tex> равна <tex>j - i</tex>, а указатель <tex>j</tex> увеличивается по единице на каждой итерации первого вложенного цикла <tex>while</tex>, то этот цикл выполнит не более <tex>r \cdot p + p - 2</tex> итераций. Худшим случаем является случай <tex>r = 1</tex>, и мы получаем, что первый вложенный цикл <tex>while</tex> всякий раз выполняет не более <tex>2p - 2</tex> итераций. Вспоминая, что всего выводится <tex>n</tex> символов, получаем, что для вывода <tex>n</tex> символов требуется не более <tex>2n - 2</tex> итераций первого вложенного <tex>while</tex>. Следовательно, алгоритм Дюваля выполняется за <tex>O(n)</tex>. |
− | Легко оценить и число сравнений символов, выполняемых алгоритмом Дюваля. Поскольку каждая итерация первого вложенного цикла while производит два сравнения символов, а также одно сравнение производится после последней итерации цикла, то общее число сравнений символов не превосходит <tex>4n - 3</tex>. | + | Легко оценить и число сравнений символов, выполняемых алгоритмом Дюваля. Поскольку каждая итерация первого вложенного цикла <tex>while</tex> производит два сравнения символов, а также одно сравнение производится после последней итерации цикла, то общее число сравнений символов не превосходит <tex>4n - 3</tex>. |
Версия 22:26, 2 мая 2014
Декомпозиция Линдона была изобретена Роджером Линдоном (англ. Roger Lyndon) в 1954 году. Она используется для нахождения лексикографически минимального и максимального суффиксов строки.
Содержание
Основные определения
Определение: |
Простая строка — строка, которая лексикографически меньше любого своего суффикса. |
Примеры:
— простая строка, так как , , , .
— не простая строка, так как .
Определение: |
Декомпозиция Линдона (англ. Lyndon decomposition) строки | — её разложение , где строки просты, и при этом .
Существование и единственность
Лемма: |
1. 2. — простая |
Доказательство: |
1. Так как , и ,2. Пусть — суффикс строки1) по пункту 1.2) 3) — суффикс . Так как — простая, , . Так как — простая, и |
Теорема: |
Можно построить декомпозицию Линдона любой строки , причем единственным образом. |
Доказательство: |
1. Существование. Разобьем строку на символы. Будем их склеивать, если подряд идущие символы: . Так как символ — простая строка, по лемме — тоже простая и . Далее склеиваем строки, не удовлетворяющие условию . Это конечный процесс, так как длина строки конечна получим нужное разбиение.Пусть существует хотя бы одно разбиение строки на простые слова. Возьмем разбиение строки на простые слова (без условия ) такое, чтобы было минимально. Пусть в нем есть , тогда эти строки можно сконкатернировать получим разбиение с меньшим числом слов — противоречие с выбором .Получили: — минимально нет2. Единственность. Пусть существует несколько разбиений , удовлетворяющих условию теоремы. Сравним длины первых двух слов и , если , сравним вторые и так далее. Если у всех слов длины одинаковы, то разбиения совпадают — противоречие. Иначе Покажем, что такого не может быть:1) Пусть Тогда , где — префикс , Получаем: (так как простая и по определению меньше своего суффикса), (так как — префикс), (по условию разбиения), (их начало совпадает, и по предположению. Получили противоречие: .2) Пусть То есть не может быть строк — проверяется аналогично. несовпадающей длины разбиения равны. |
Алгоритм Дюваля
Алгоритм
Алгоритм Дюваля (англ. Duval's algorithm) находит для данной строки длины
декомпозицию Линдона за время с использованием дополнительной памяти. Он строит декомпозицию только на упорядоченных алфавитах. Алгоритм Дюваля относится к классу жадных алгоритмов.
Определение: |
Предпростая строка — строка | , такая что , где — некоторая простая строка, а — некоторый префикс строки .
Во время работы алгоритма строка разделена на три строки , где в строке декомпозиция Линдона уже найдена и уже больше не используется алгоритмом; строка — это предпростая строка (причём длину простых строк внутри неё мы также запоминаем); строка — это ещё не обработанная часть строки . Алгоритм Дюваля берёт первый символ строки и пытается дописать его к строке . При этом, возможно, для какого-то префикса строки декомпозиция Линдона становится известной, и эта часть переходит к строке .
Будем поддерживаться указатель
на начало строки . Внешний цикл алгоритма будет выполняться, пока , то есть пока вся строка не перейдёт в строку . Внутри этого цикла создаются два указателя: указатель на начало строки и указатель на текущий символ в строке , с которым будет производиться сравнение. Затем будем в цикле пытаться добавить символ к строке , для чего необходимо произвести сравнение с символом . Здесь у нас возникают три различных случая:1. Если
, то мы можем дописать символ к строке , не нарушив её "предпростоты". Следовательно, в этом случае мы просто увеличиваем указатели и на единицу.2. Если
, то, очевидно, строка станет простой. Тогда мы увеличиваем на единицу, а передвигаем обратно на , чтобы следующий символ сравнивался с первым символом .3. Если
, то строка уже не может быть предпростой. Поэтому мы разбиваем предпростую строку на простые строки плюс "остаток" (префикс простой строки, возможно, пустой); простые строки добавляем в ответ (т.е. выводим их позиции, попутно передвигая указатель ), а "остаток" вместе с символом переводим обратно в строку , и останавливаем выполнение внутреннего цикла. Тем самым мы на следующей итерации внешнего цикла заново обработаем остаток, зная, что он не мог образовать предпростую строку с предыдущими простыми строками. Осталось только заметить, что при выводе позиций простых строк нам нужно знать их длину; но она, очевидно, равна .Реализация
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; } }
Корректность
Асимптотика
Дополнительная память требуется только на три указателя:
.Внешний цикл
делает не более итераций, поскольку в конце каждой его итерации к результату добавляется как минимум один символ (а всего символов ).Оценим теперь количество итераций первого вложенного цикла
. Для этого рассмотрим второй вложенный цикл — он при каждом своём запуске выводит некоторое количество копий одной и той же простой строки некоторой длины . Заметим, что строка является предпростой, причём её простые строки имеют длину как раз , т.е. её длина не превосходит . Поскольку длина строки равна , а указатель увеличивается по единице на каждой итерации первого вложенного цикла , то этот цикл выполнит не более итераций. Худшим случаем является случай , и мы получаем, что первый вложенный цикл всякий раз выполняет не более итераций. Вспоминая, что всего выводится символов, получаем, что для вывода символов требуется не более итераций первого вложенного . Следовательно, алгоритм Дюваля выполняется за .Легко оценить и число сравнений символов, выполняемых алгоритмом Дюваля. Поскольку каждая итерация первого вложенного цикла
производит два сравнения символов, а также одно сравнение производится после последней итерации цикла, то общее число сравнений символов не превосходит .