Декомпозиция Линдона — различия между версиями
Shersh (обсуждение | вклад) |
|||
Строка 1: | Строка 1: | ||
− | Декомпозиция Линдона была изобретена Роджером Линдоном (англ. ''Roger Lyndon'') в 1954 году. Она используется для нахождения лексикографически минимального и максимального суффиксов строки. | + | Декомпозиция Линдона была изобретена Роджером Линдоном (англ. ''Roger Lyndon'') в 1954 году. Она используется для нахождения лексикографически минимального и максимального суффиксов строки, а также лексикографически минимального циклического сдвига. |
==Основные определения== | ==Основные определения== | ||
Строка 28: | Строка 28: | ||
2. <tex>s + t</tex> {{---}} простая | 2. <tex>s + t</tex> {{---}} простая | ||
|proof= | |proof= | ||
− | 1. Так как <tex>s < t</tex>, <tex>\ | + | 1. Так как <tex>s < t</tex>, то <tex>\exists i : s[i] < t[i]</tex> и <tex>s[j] = t[j]</tex>, <tex>j < i \Rightarrow s + t < t</tex> |
− | 2. Пусть <tex>u</tex> {{---}} суффикс строки <tex>s + t</tex> | + | 2. Пусть <tex>u</tex> {{---}} суффикс строки <tex>s + t</tex>. Тогда рассмотрим <tex> 3 </tex> возможных случая: |
− | + | * <tex>|u| = |t| \Rightarrow u = t \Rightarrow u > s + t</tex> по пункту <tex> 1 </tex>. | |
− | + | * <tex>|u| < |t| \Rightarrow u</tex> {{---}} суффикс <tex>t</tex>. Так как <tex>t</tex> {{---}} простая, и <tex>t < u </tex> по определению <tex> \Rightarrow s + t < t < u</tex> | |
− | + | * <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> | |
− | |||
− | |||
}} | }} | ||
{{Теорема | {{Теорема | ||
|id=theorem | |id=theorem | ||
+ | |author=Чен-Линдон-Фокс | ||
|statement=Можно построить декомпозицию Линдона любой строки <tex>s</tex>, причем единственным образом. | |statement=Можно построить декомпозицию Линдона любой строки <tex>s</tex>, причем единственным образом. | ||
|proof= | |proof= | ||
1. Существование. | 1. Существование. | ||
− | + | У каждой строки существует хотя бы одно разбиение на простые слова. Это следует из того, что отдельный символ является простым словом. Тогда среди всех разбиений строки на простые слова возьмём то, в котором меньше всего слов. Покажем, что это и будет декомпозицией Линдона данной строки. | |
− | |||
− | |||
− | + | Предположим, что это не так. Значит, <tex>\exists i : s_i < s_{i+1}</tex>. Так как слова <tex> s_i </tex> и <tex> s_{i+1} </tex> простые, то из доказанной [[Декомпозиция Линдона#lemma | леммы]] следует, что эти слова можно сконкатенировать и получить разбиение строки <tex> s </tex> на меньшее число слов. Получили противоречие. | |
− | |||
− | |||
− | + | Таким образом доказали даже более сильное утверждение: <tex>s = s_1 s_2 ... s_k</tex>, <tex> k </tex> {{---}} минимально <tex>\Leftrightarrow</tex> нет <tex>s_i < s_{i+1}</tex> | |
2. Единственность. | 2. Единственность. | ||
Строка 61: | Строка 56: | ||
Сравним длины первых двух слов <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>, сравним вторые и так далее. | ||
Если у всех слов длины одинаковы, то разбиения совпадают {{---}} противоречие. | Если у всех слов длины одинаковы, то разбиения совпадают {{---}} противоречие. | ||
− | Иначе <tex>\ | + | Иначе <tex>\exists s_i : |s_i| \neq |s_i'|</tex>. |
+ | |||
Покажем, что такого не может быть: | Покажем, что такого не может быть: | ||
− | 1) Пусть <tex>|s_i| > |s_i'|</tex> | + | 1) Пусть <tex>|s_i| > |s_i'|</tex>, тогда <tex>s_i = s_i's_{i+1}'...t</tex>, где <tex>t</tex> {{---}} префикс <tex>s_{j+1}'</tex>, <tex>i < j</tex>. |
− | + | ||
− | Получаем: <tex>s_i < t</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> {{---}} префикс ???), |
<tex>s_{j+1}' < s_i'</tex> (по условию разбиения), | <tex>s_{j+1}' < s_i'</tex> (по условию разбиения), | ||
<tex>s_i' < s_i</tex> (их начало совпадает, и <tex>|s_i| < |s_i'|</tex> по предположению. | <tex>s_i' < s_i</tex> (их начало совпадает, и <tex>|s_i| < |s_i'|</tex> по предположению. | ||
Получили противоречие: <tex>s_i < s_i</tex>. | Получили противоречие: <tex>s_i < s_i</tex>. | ||
− | 2) | + | 2) Случай <tex>|s_i| < |s_i'|</tex> симметричен разобранному. |
− | То есть не может быть строк <tex>s_i</tex> несовпадающей длины | + | |
+ | То есть не может быть строк <tex>s_i</tex> несовпадающей длины, значит, разбиения равны. | ||
}} | }} | ||
Строка 124: | Строка 121: | ||
Дополнительная память требуется только на три указателя: <tex>i, j, k</tex>. | Дополнительная память требуется только на три указателя: <tex>i, j, k</tex>. | ||
− | Внешний цикл <tex>while</tex> делает не более <tex>n</tex> итераций, поскольку в конце каждой его итерации к результату добавляется как минимум один символ (а всего символов <tex>n</tex>). | + | Внешний цикл <tex>\mathrm{while}</tex> делает не более <tex>n</tex> итераций, поскольку в конце каждой его итерации к результату добавляется как минимум один символ (а всего символов <tex>n</tex>). |
− | Оценим теперь количество итераций первого вложенного цикла <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>. | + | Оценим теперь количество итераций первого вложенного цикла <tex>\mathrm{while}</tex>. Для этого рассмотрим второй вложенный цикл <tex>\mathrm{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>\mathrm{while}</tex>, то этот цикл выполнит не более <tex>r \cdot p + p - 2</tex> итераций. Худшим случаем является случай <tex>r = 1</tex>, и мы получаем, что первый вложенный цикл <tex>\mathrm{while}</tex> всякий раз выполняет не более <tex>2p - 2</tex> итераций. Вспоминая, что всего выводится <tex>n</tex> символов, получаем, что для вывода <tex>n</tex> символов требуется не более <tex>2n - 2</tex> итераций первого вложенного <tex>\mathrm{while}</tex>. Следовательно, алгоритм Дюваля выполняется за <tex>O(n)</tex>. |
− | Легко оценить и число сравнений символов, выполняемых алгоритмом Дюваля. Поскольку каждая итерация первого вложенного цикла <tex>while</tex> производит два сравнения символов, а также одно сравнение производится после последней итерации цикла, то общее число сравнений символов не превосходит <tex>4n - 3</tex>. | + | Легко оценить и число сравнений символов, выполняемых алгоритмом Дюваля. Поскольку каждая итерация первого вложенного цикла <tex>\mathrm{while}</tex> производит два сравнения символов, а также одно сравнение производится после последней итерации цикла, то общее число сравнений символов не превосходит <tex>4n - 3</tex>. |
Версия 12:38, 3 мая 2014
Декомпозиция Линдона была изобретена Роджером Линдоном (англ. Roger Lyndon) в 1954 году. Она используется для нахождения лексикографически минимального и максимального суффиксов строки, а также лексикографически минимального циклического сдвига.
Содержание
Основные определения
Определение: |
Простая строка — строка, которая лексикографически меньше любого своего суффикса. |
Примеры:
— простая строка, так как , , , .
— не простая строка, так как .
Определение: |
Декомпозиция Линдона (англ. Lyndon decomposition) строки | — её разложение , где строки просты, и при этом .
Существование и единственность
Лемма: |
1. 2. — простая |
Доказательство: |
1. Так как , то и ,2. Пусть — суффикс строки . Тогда рассмотрим возможных случая:
|
Теорема (Чен-Линдон-Фокс): |
Можно построить декомпозицию Линдона любой строки , причем единственным образом. |
Доказательство: |
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; } }
Корректность
Асимптотика
Дополнительная память требуется только на три указателя:
.Внешний цикл
делает не более итераций, поскольку в конце каждой его итерации к результату добавляется как минимум один символ (а всего символов ).Оценим теперь количество итераций первого вложенного цикла
. Для этого рассмотрим второй вложенный цикл — он при каждом своём запуске выводит некоторое количество копий одной и той же простой строки некоторой длины . Заметим, что строка является предпростой, причём её простые строки имеют длину как раз , т.е. её длина не превосходит . Поскольку длина строки равна , а указатель увеличивается по единице на каждой итерации первого вложенного цикла , то этот цикл выполнит не более итераций. Худшим случаем является случай , и мы получаем, что первый вложенный цикл всякий раз выполняет не более итераций. Вспоминая, что всего выводится символов, получаем, что для вывода символов требуется не более итераций первого вложенного . Следовательно, алгоритм Дюваля выполняется за .Легко оценить и число сравнений символов, выполняемых алгоритмом Дюваля. Поскольку каждая итерация первого вложенного цикла
производит два сравнения символов, а также одно сравнение производится после последней итерации цикла, то общее число сравнений символов не превосходит .