Главная страница
Навигация по странице:

  • 10.8.5. Как связаны суффиксные ссылки двух соседних вершин (от- ца и сына)

  • А. шень Издание шестое, дополненное Москва


    Скачать 1.62 Mb.
    НазваниеА. шень Издание шестое, дополненное Москва
    Дата17.09.2022
    Размер1.62 Mb.
    Формат файлаpdf
    Имя файлаshen-progbook.pdf
    ТипКнига
    #681926
    страница13 из 19
    1   ...   9   10   11   12   13   14   15   16   ...   19
    10.7.13. Где ещё используется то же самое рассуждение?
    Ответ. В алгоритме Флойда вычисления цены кратчайшего пути,
    см. главу
    9
    (Разные алгоритмы на графах).
    
    10.7.14. Докажите, что класс множеств, задаваемых регулярными выражениями, не изменился бы, если бы мы разрешили использовать не только объединение, но и отрицание (а следовательно, и пересечение |
    оно выражается через объединение и отрицание).
    Решение. Для автоматов переход к отрицанию очевиден.
    
    Замечание. На практике важную роль играет число состояний авто- мата. Оказывается, что тут всё не так просто, и переход от источника к автомату требует экспоненциального роста числа состояний. Подроб- ное рассмотрение связанных с этим теоретических и практических во- просов | дело особое (см. книгу Ахо, Ульмана и Сети о компиляторах).
    10.8. Суффиксные деревья
    До сих про наши программы сначала получали образец, который на- до искать, а потом текст, в котором надо искать. В следующих задачах всё наоборот.
    10.8.1. Программа получает на вход слово 𝑌 длины 𝑚 и может его обрабатывать (пока без ограничений на время и память). Затем она получает слово 𝑋 длины 𝑛 и должна сообщить, является ли оно подсло- вом слова 𝑌 . При этом число операций при обработке слова 𝑋 должно быть порядка 𝑛 (не превосходить 𝑐𝑛, где константа 𝑐 может зависеть от размера алфавита). Как написать такую программу?
    Решение. Пока не накладывается никаких ограничений на время и память при обработке 𝑌 , это не представляет труда. Именно, надо склеить все подслова слова 𝑌 в дерево, объединив слова с общими нача-

    198 10. Сопоставление с образцом лами (как мы это делали, распознавая вхождения нескольких образцов).
    Например, для 𝑌 = 𝑎𝑏𝑎𝑏𝑐 получится такое дерево подслов (на ребре на- писана буква, которая добавляется при движении по этому ребру; вер- шины находятся во взаимно однозначном соответствии с подсловами слова 𝑌 ):
    𝑏
    𝑎
    𝑐
    𝑏
    𝑏
    𝑏
    𝑎
    𝑎
    𝑐
    𝑐
    𝑐
    𝑐
    Пусть такое дерево построено. После этого, читая слово 𝑋 слева на- право, мы прослеживаем 𝑋 в дереве, начав с корня; слово 𝑋 будет под- словом слова 𝑌 , если при этом мы не выйдем за пределы дерева.
    
    Заметим, что аналогичная конструкция годится для любого множе- ства слов 𝑈, а не только для множества всех подслов данного слова: по- сле того как соответствующее дерево построено, мы можем про любое слово 𝑋 определить его принадлежность к 𝑈 за время, пропорциональ- ное длине 𝑋. (Надо только дополнительно хранить в вершине дерева информацию, принадлежит ли соответствующее ей слово множеству 𝑈
    или лишь является началом другого слова, принадлежащего 𝑈.)
    10.8.2. Решите предыдущую задачу с дополнительным ограничени- ем: объём используемой памяти пропорционален длине слова 𝑌 .
    Решение. Прежний способ не годится: число вершин дерева равно числу подслов слова 𝑌 , а у слова длины 𝑚 число подслов может быть порядка 𝑚
    2
    , а не 𝑚. Однако мы можем «сжать» наше дерево, оставив вершинами лишь точки ветвления (где больше одного сына). Тогда на рёбрах дерева надо написать уже не буквы, а куски слова 𝑌 .
    Вот что получится при сжатии нашего примера:
    𝑏
    𝑎𝑏
    𝑐
    𝑐
    𝑎𝑏𝑐
    𝑐
    𝑎𝑏𝑐
    Будем считать (здесь и далее), что последняя буква слова 𝑌 больше в нём не встречается. (Этого всегда можно достичь, дописав допол- нительный фиктивный символ.) Тогда листья сжатого дерева соответ- ствуют концам слова 𝑌 , а внутренние вершины (точки ветвления) |

    10.8. Суффиксные деревья
    199
    таким подсловам 𝑠 слова 𝑌 , которые встречаются в 𝑌 несколько раз,
    и притом с разными буквами после 𝑠.
    У каждой внутренней вершины (не листа) сжатого дерева есть не менее двух сыновей. В деревьях с такими свойствами число внутренних вершин не превосходит числа листьев. (В самом деле, при движении сле- ва направо в каждой точке ветвления добавляется новый путь к листу.)
    Поскольку листьев 𝑚, всего вершин не более 2𝑚, и мы уложимся в ли- нейную по 𝑚 память, если будем экономно хранить пометки на рёбрах.
    Каждая такая пометка является подсловом слова 𝑌 , и потому доста- точно указывать координату её начала и конца в 𝑌 . Это не помешает впоследствии прослеживать произвольное слово 𝑋 в этом дереве буква за буквой, просто в некоторые моменты мы будем находиться внутри рёбер (и должны помнить, внутри какого ребра и в какой позиции мы находимся). При появлении новой буквы слова 𝑋 её нужно сравнить с соответствующей буквой пометки этого ребра (что можно сделать за
    𝑂(1) действий, так как координату этой буквы мы знаем.)
    
    Построенное нами сжатое дерево называют сжатым суффиксным деревом слова 𝑌 (концы слова называют «суффиксами»).
    10.8.3. Покажите, что построение сжатого суффиксного дерева можно выполнить за время 𝑂(𝑚
    2
    ) с использованием 𝑂(𝑚) памяти.
    Решение. Будем добавлять в суффиксное дерево суффиксы по очере- ди. Добавление очередного суффикса делается так же, как и проверка принадлежности: мы читаем его буква за буквой и прокладываем путь в дереве. В некоторый момент добавляемый суффикс выйдет за пре- делы дерева (напомним, что мы считаем, что последний символ слова уникален).
    Если это произойдёт посередине ребра, то ребро придётся в этом месте разрезать. Ребро превратится в два, его пометка разрежется на две, появится новая вершина (точка ветвления) и её новый сын-лист.
    Если точка ветвления совпадёт с уже имевшейся в дереве, то у неё
    появится новый сын-лист. В любом случае после обнаружения места ветвления требуется 𝑂(1) операций для перестройки дерева (в частно- сти, разрезание пометки на две выполняется легко, так как пометки хранятся в виде координат начала и конца в слове 𝑌 ).
    
    Гораздо более сложной задачей является построение сжатого суф- фиксного дерева за линейное время (вместо квадратичного, как в пре- дыдущей задаче). Чтобы изложить алгоритм Маккрейта, который ре- шает эту задачу, нам понадобятся некоторые приготовления.
    Для начала опишем более подробно структуру дерева, которое мы используем, и операции с ним.

    200 10. Сопоставление с образцом
    Мы рассматриваем деревья с корнем, на рёбрах которых написаны слова (пометки); все пометки являются подсловами некоторого заранее фиксированного слова 𝑌 . При этом выполнены такие свойства:

    каждая внутренняя вершина имеет хотя бы двух сыновей;

    пометки на рёбрах, выходящих из данной вершины, начинаются на разные буквы.
    Каждой вершине 𝑣 такого дерева соответствует слово, которое за- писано на пути от корня 𝑟 к вершине 𝑣. Будем обозначать это слово 𝑠(𝑣).
    Обозначим пометку на ребре, ведущем к 𝑣, через 𝑙(𝑣), а отца верши- ны 𝑣 | через 𝑓(𝑣). Тогда 𝑠(𝑟) = ˜ (пустое слово), а
    𝑠(𝑣) = 𝑠(𝑓(𝑣)) + 𝑙(𝑣)
    для любой вершины 𝑣 ̸= 𝑟 (знак «+» обозначает соединение строк).
    Помимо вершин дерева, мы будем рассматривать позиции в нём, ко- торые могут быть расположены в вершинах, а также «внутри рёбер»
    (разделяя пометку этого ребра на две части). Формально говоря, по- зиция представляет собой пару (𝑣, 𝑘), где 𝑣 | вершина (отличная от корня), а 𝑘 | целое число в промежутке [0, |𝑙(𝑣)|), указывающее, на сколько букв надо вернуться от 𝑣 к корню. Здесь |𝑙(𝑣)| | длина по- метки 𝑙(𝑣); значение 𝑘 = 𝑙(𝑣) соответствовало бы предыдущей вершине и потому не допускается. К числу позиций мы добавляем также па- ру (𝑟, 0), соответствующую корню дерева. Каждой позиции 𝑝 = (𝑣, 𝑘)
    соответствует слово 𝑠(𝑝), которое получается удалением 𝑘 последних символов из 𝑠(𝑣).
    Пусть 𝑝 | произвольная позиция в дереве, а 𝑤 | слово. Пройти вдоль 𝑤, начиная с 𝑝, означает найти другую позицию 𝑞, для которой
    𝑠(𝑞) = 𝑠(𝑝) + 𝑤. Если такая позиция есть, то (при описанном способе хранения пометок, когда указываются координаты их начала и конца внутри 𝑌 ) её можно найти за время, пропорциональное длине слова 𝑤.
    Если такой позиции нет, то в какой-то момент мы «свернём с пути»;
    в этот момент можно пополнить дерево, сделав отсутствующую в де- реве часть слова 𝑤 пометкой на пути к новому листу. Надо только,
    чтобы эта пометка была подсловом слова 𝑌 (при нашем способе хране- ния пометок); это будет гарантировано, если прослеживаемое слово 𝑤
    является подсловом слова 𝑌 .
    Заметим, что при этом может образоваться новая вершина (если развилка оказалась внутри ребра), а может и не образоваться (если раз- вилка оказалась в вершине). Число действий при такой модификации

    10.8. Суффиксные деревья
    201
    пропорционально длине пройденной части слова (длина непройденной не важна).
    Оказывается, что навигацию в дереве можно ускорить, если заранее известно, что она будет успешной.
    10.8.4. Пусть для данной позиции 𝑝 и слова 𝑤 заранее известно,
    что в дереве есть позиция 𝑞, для которой 𝑠(𝑞) = 𝑠(𝑝) + 𝑤. Покажите,
    что позицию 𝑞 можно найти за время, пропорциональное числу рёбер дерева на пути от 𝑝 к 𝑞. (Это число может быть значительно меньше длины слова 𝑤, если пометки на рёбрах длинные.)
    Решение. В самом деле, при навигации нужно ориентироваться лишь в вершинах (выбирать исходящее ребро в зависимости от очередной буквы); в остальных местах путь однозначный и потому можно сдви- гаться сразу к концу ребра.
    
    Подведём итоги. Рассмотренный способ хранения деревьев позволя- ет (для фиксированного слова 𝑌 )

    создать дерево из одного корня [𝑂(1)];

    найти отца любой вершины (кроме корня) [𝑂(1)];

    узнать пометку любой вершины (кроме корня), то есть пометку ведущего к ней ребра [𝑂(1)];

    пройти из любой позиции 𝑝 вдоль любого слова 𝑤, если заранее известно, что мы не выйдем из дерева; результат | позиция 𝑞
    в дереве, для которой 𝑠(𝑞) = 𝑠(𝑝) + 𝑤 [𝑂(число рёбер на пути)];

    добавить слово 𝑤 (некоторое подслово слова 𝑌 ), начав с позиции 𝑝;
    если при этом в дереве нет позиции 𝑞, для которой 𝑠(𝑞) = 𝑠(𝑝) +
    + 𝑤, то дерево меняется и такая позиция 𝑞 создаётся (она будет листом) [𝑂(число букв в 𝑤, не вошедших в 𝑙(𝑞)];

    наконец, для любого слова 𝑋 можно выяснить, найдётся ли в де- реве позиция 𝑞, для которой 𝑠(𝑞) = 𝑋 [𝑂(|𝑋|)].
    В квадратных скобках указано число действий при выполнении со- ответствующих операций.
    Ещё мы будем хранить в вершинах дерева «суффиксные ссылки»
    (в каждой вершине будет не более одной ссылки на другую вершину),
    но сначала надо объяснить, что это такое.
    Начнём с полного (не сжатого) суффиксного дерева для слова 𝑌 .
    Каждой его вершине (кроме корня) отвечает некоторое непустое под- слово слова 𝑌 . Если мы отрежем у этого подслова последнюю букву,

    202 10. Сопоставление с образцом то в дереве спустимся на один шаг к корню. Но что будет, если мы отрежем первую букву? Снова получится подслово, но оно уже будет совсем в другом месте дерева.
    Вот как выглядят эти переходы в нашем примере (отрезание первой буквы соответствует пунктирной стрелке):
    𝑏
    𝑎
    𝑐
    𝑏
    𝑏
    𝑏
    𝑎
    𝑎
    𝑐
    𝑐
    𝑐
    𝑐
    Эти стрелки мы будем называть суффиксными ссылками, посколь- ку они соответствуют переходу от слова к его суффиксу на единицу меньшей длины. Они определены для всех вершин, кроме корня.
    Формально можно сказать так. Пусть 𝑤

    означает слово 𝑤 без пер- вой буквы (𝑤

    определено для любого непустого слова 𝑤). Тогда суф- фиксная ссылка ведёт из вершины 𝑝 в вершину 𝑞, если 𝑠(𝑞) = 𝑠(𝑝)

    (на- помним, что 𝑠(𝑢) | слово, соответствующее вершине 𝑢).

    10.8.5. Как связаны суффиксные ссылки двух соседних вершин (от- ца и сына)?
    Ответ. Они указывают на соседние вершины, и буква на соединяю- щем их ребре та же самая.
    
    10.8.6. Докажите, что при переходе к сжатому суффиксному дереву ссылки по-прежнему идут из вершины в вершину (а не внутрь рёбер).
    Решение. В самом деле, по нашему предположению последняя бу- ква слова больше в нём не встречается, поэтому из листа ссылка ведёт в лист. А если вершина (отличная от корня) является точкой ветвле- ния, то соответствующее ей слово 𝑠 встречается с различными буквами после него. Другими словами, для некоторых букв 𝑎 и 𝑏 слова 𝑠𝑎 и 𝑠𝑏
    являются подсловами слова 𝑌 . Отрезав от них первую букву, получим слова 𝑠

    𝑎 и 𝑠

    𝑏, которые также являются подсловами слова 𝑌 , поэтому и 𝑠

    является точкой ветвления.
    
    Вот что получится для нашего примера:
    𝑏
    𝑎𝑏
    𝑐
    𝑐
    𝑎𝑏𝑐
    𝑐
    𝑎𝑏𝑐

    10.8. Суффиксные деревья
    203
    Теперь мы уже готовы к изложению алгоритма Маккрейта. Сжа- тое суффиксное дерево строим постепенно, добавляя к нему суффиксы по мере уменьшения их длины. Обозначим через 𝑌
    𝑖
    суффикс, начинаю- щийся с 𝑖-й буквы слова 𝑌 . (Таким образом, 𝑌
    1
    = 𝑌 , а 𝑌
    𝑚
    состоит из одной буквы.) После 𝑖 шагов построения наше дерево будет хранить
    𝑌
    1
    , . . . , 𝑌
    𝑖
    10.8.7. Покажите, что суффиксные ссылки в таком дереве опреде- лены корректно (ведут в другую вершину того же дерева) для всех вер- шин, кроме, возможно, последнего добавленного листа (соответствую- щего слову 𝑌
    𝑖
    ) и его отца.
    Решение. В самом деле, суффиксная ссылка из листа 𝑌
    𝑗
    ведёт в лист 𝑌
    𝑗+1
    , и потому ей есть куда вести. Рассмотрим теперь внутрен- нюю вершину 𝑣, не являющуюся отцом последнего листа. Пусть в ней разветвляются два пути в листья 𝑌
    𝑗
    и 𝑌
    𝑘
    . Без ограничения общности можно считать, что 𝑗, 𝑘 < 𝑖 (если один из путей ведёт в 𝑌
    𝑖
    , то его можно заменить другим, ведь вершина 𝑣 по предположению не последняя раз- вилка на этом пути). Отрезав от этих путей первый символ, получим пути в листья 𝑌
    𝑗+1
    и 𝑌
    𝑘+1
    ; эти пути присутствуют в дереве (посколь- ку 𝑗 + 1 и 𝑘 + 1 не превосходят 𝑖), а точка их развилки будет концом суффиксной ссылки вершины 𝑣.
    
    Суффиксные ссылки для листьев нам не понадобятся, и вычислять мы их не будем, а для всех остальных вершин дерева мы их будем вы- числять и хранить. Более точно, после 𝑖 шагов алгоритма

    в дереве хранятся слова 𝑌
    1
    , . . . 𝑌
    𝑖
    (и все их начала);

    адрес листа, соответствующего последнему добавленному суф- фиксу (𝑌
    𝑖
    ) хранится в переменной last;

    для всех внутренних вершин дерева, кроме, быть может, отца вер- шины last, хранится правильная суффиксная ссылка.
    Надо понять, как поддерживать это при добавлении очередного суффикса. Можно, не мудрствуя лукаво, добавлять 𝑌
    𝑖+1
    буква за бу- квой, начиная с корня дерева. (Именно так мы раньше и делали, и это требовало квадратичного времени.)
    Какие тут возможны оптимизации? Первая связана с тем, что мы можем двигаться по дереву быстрее, если знаем, что заведомо из него не выйдем. Вторая связана с использованием суффиксных ссылок.
    Оба варианта предполагают, что отец 𝑢 листа last не совпадает с корнем. (Если совпадает, нам придётся добавлять 𝑌
    𝑖+1
    от корня.)

    204 10. Сопоставление с образцом
    Пусть tail | пометка листа last, а head = 𝑠(𝑢); другими словами, слово head соответствует вершине 𝑢. Тогда
    𝑌
    𝑖
    = head + tail.
    Отрезая первую букву, получаем
    𝑌
    𝑖+1
    = head

    + tail.
    Заметим, что head

    заведомо не выходит за пределы дерева. В са- мом деле, 𝑢 было точкой ветвления, поэтому помимо листа 𝑌
    𝑖
    через точку 𝑢 проходил и лист 𝑌
    𝑗
    с 𝑗 < 𝑖. Тогда 𝑌
    𝑗
    начинается на head, а 𝑌
    𝑗+1
    начинается на head

    и уже есть в дереве.
    Поэтому мы можем сначала проследить head

    (найти позицию 𝑣, для которой 𝑠(𝑣) = head

    ), а потом уже добавить tail, начиная с 𝑣.
    head tail last [𝑌
    i
    ]
    [𝑌
    j
    ]
    𝑢
    Первый способ оптимизации: head

    заведомо есть в дереве.
    Эта оптимизация никак не использует суффиксных ссылок. Второй способ оптимизации их использует и позволяет (в том случае, когда применим) обойтись без прослеживания 𝑌
    𝑖+1
    от корня (как ускорен- ного, так и обычного). Пусть на пути к листу last, представляющему суффикс 𝑌
    𝑖
    , имеется вершина 𝑣, у которой суффиксная ссылка указы- вает на вершину 𝑤, так что 𝑠(𝑤) = 𝑠(𝑣)

    . Пусть 𝑝 | слово на пути от 𝑣
    к last. Тогда
    𝑌
    𝑖
    = 𝑠(𝑣) + 𝑝, 𝑌
    𝑖+1
    = 𝑌

    𝑖
    = 𝑠(𝑣)

    + 𝑝 = 𝑠(𝑤) + 𝑝,
    и для добавления 𝑌
    𝑖+1
    в дерево достаточно добавить слово 𝑝, начиная с вершины 𝑤.
    Второй способ оптимизации сочетается с первым: отрезок слова 𝑝
    от 𝑣 до отца листа last можно проходить с уверенностью, что мы не выйдем за пределы дерева.
    Итак, мы можем описать действия, выполняемые при добавлении очередного суффикса 𝑌
    𝑖+1
    в дерево, следующим образом.

    10.8. Суффиксные деревья
    205
    𝑝
    𝑝
    last [𝑌
    i
    ]
    [𝑌
    i+1
    ]
    𝑣
    𝑤
    Второй способ оптимизации: пользуемся суффиксной ссылкой вершины на пути к last.
    Пусть 𝑢 | отец листа last, соответствующего последнему уже до- бавленному суффиксу 𝑌
    𝑖
    Случай 1: 𝑢 есть корень дерева. Тогда ни одна из оптимизаций не применима, и мы добавляем 𝑌
    𝑖+1
    , начиная от корня.
    Случай 2: 𝑢 не есть корень дерева, но отец 𝑢 есть корень дере- ва (лист last находится на высоте 2). Тогда 𝑌
    𝑖
    = head + tail, где head и tail | пометки вершин 𝑢 и last. Мы применяем первую оптимизацию и прослеживаем head

    с гарантией до некоторой позиции 𝑧, а потом добавляем tail от 𝑧.
    Случай 3: 𝑢 не есть корень дерева и его отец 𝑣 также не есть корень дерева. Тогда для 𝑣 имеется суффиксная ссылка на некото- рую вершину 𝑤, и 𝑠(𝑤) = 𝑠(𝑣)

    . Пусть pretail | пометка вершины 𝑢,
    а tail | пометка листа last, при этом 𝑌
    𝑖
    = 𝑠(𝑣) + pretail + tail и потому
    𝑌
    𝑖+1
    = 𝑌

    𝑖
    = 𝑠(𝑤) + pretail + tail. Остаётся проследить pretail от верши- ны 𝑤 с гарантией, получив некоторую позицию 𝑧, а потом добавить tail от вершины 𝑧.
    Остаётся ещё понять, как поддерживать структуру суффиксных ссылок (адрес нового листа у нас получается при добавлении сам собой,
    так что с ним проблем нет) и оценить число действий при выполнении этих процедур последовательно для всех суффиксов.
    Начнём с суффиксных ссылок. По правилам они должны быть у всех внутренних вершин, кроме отца только что добавленного листа. Поэто- му нам надо заботиться об отце листа last, соответствующего 𝑌
    𝑖
    (этот лист перестал быть «только что добавленным»; напротив, единственная новая вершина как раз является отцом только что добавленного листа и в ней суффиксная ссылка не нужна). Это актуально в случаях 2 и 3,
    но в этих случаях по ходу дела была найдена нужная вершина 𝑧, куда и будет направлена суффиксная ссылка из 𝑢. Строго говоря, 𝑧 могла быть не вершиной, а позицией, но тогда после добавления она станет вершиной (отцом только что добавленного листа) | ведь в новом де-

    206 10. Сопоставление с образцом реве 𝑢 уже не является отцом последнего листа, и потому суффиксная ссылка из 𝑢, как было доказано, должна вести в вершину. (Другими словами, в случаях 2 и 3, если позиция 𝑧 была внутри ребра, то в ней ребро разрезается.)
    Всё сказанное можно условно записать в виде такого алгоритма до- бавления суффикса 𝑌
    𝑖+1
    :
    {
    дерево содержит суффиксы 𝑌
    1
    , . . . , 𝑌
    𝑖
    𝑠(last) = 𝑌
    𝑖
    имеются корректные суффиксные ссылки для всех внутренних вершин, кроме отца листа last }
    𝑢 := отец листа last;
    tail := пометка листа last;
    {
    𝑌
    𝑖
    = 𝑠(𝑢) + tail }
    if 𝑢 = корень дерева then begin
    {
    𝑌
    𝑖+1
    = tail

    }
    добавить tail

    , начиная с корня,
    полученный лист поместить в last end else begin
    𝑣 := отец вершины 𝑢;
    pretail := пометка вершины 𝑢;
    {
    𝑌
    𝑖
    = 𝑠(𝑣) + pretail + tail }
    if 𝑣 = корень дерева then begin
    {
    𝑌
    𝑖+1
    = pretail

    + tail }
    проследить pretail

    из корня в 𝑧
    end else begin
    𝑤 := суффиксная ссылка вершины 𝑣;
    {
    𝑠(𝑤) = 𝑠(𝑣)

    , 𝑌
    𝑖+1
    = 𝑠(𝑤) + pretail + tail }
    проследить pretail из 𝑤 в 𝑧
    end;
    {
    осталось добавить tail из 𝑧 и ссылку из 𝑢 в 𝑧 }
    if позиция 𝑧 является вершиной then begin поместить в 𝑢 ссылку на 𝑧;
    добавить tail, начиная с 𝑧,
    полученный лист поместить в last;
    end else begin добавить tail, начиная с 𝑧,
    полученный лист поместить в last;
    поместить в 𝑢 ссылку на отца листа last;
    end end;

    10.8. Суффиксные деревья
    207
    Осталось оценить число действий, которые выполняются при после- довательном добавлении суффиксов 𝑌
    1
    , . . . , 𝑌
    𝑚
    . При добавлении каждо- го следующего суффикса выполняется ограниченное количество дей- ствий, если не считать действий при «прослеживании» и «добавлении».
    Нам надо установить, что общее число действий есть 𝑂(𝑚); для это- го достаточно отдельно доказать, что суммарное число действий при всех прослеживаниях есть 𝑂(𝑚) и суммарное число действий при всех добавлениях есть 𝑂(𝑚). (Заметим, что некоторые прослеживания или добавления могут быть долгими | но это компенсируется другими.)
    Прослеживания. Длительность прослеживания пропорциональна чи- слу 𝑘 задействованных в нём рёбер, но при этом высота последнего добавленного листа (число рёбер на пути к нему) увеличивается на
    𝑘 − 𝑂(1) (по сравнению с предыдущим добавленным листом). Чтобы убедиться в этом, достаточно заметить, что в третьем случае высота вершины 𝑠(𝑤) может быть меньше высоты вершины 𝑠(𝑣) разве что на единицу, поскольку суффиксные ссылки из всех вершин на пути к 𝑣 (не считая корня, где нет суффиксной ссылки) ведут в вершины на пути к 𝑤. Поскольку высота любого листа ограничена числом 𝑚, заключаем,
    что общая длительность всех прослеживаний есть 𝑂(𝑚).
    Добавления. Рассуждаем аналогично, но следим не за высотой по- следнего листа, а за длиной его пометки. При добавлении слова tail (или tail

    ) число действий пропорционально числу просмотренных букв, но каждая просмотренная буква (кроме, быть может, одной) уменьшает длину пометки хотя бы на единицу: в пометке остаются лишь непросмо- тренные буквы (не считая первой). Поэтому на все добавления уходит в общей сложности 𝑂(𝑚) действий.
    Тем самым мы доказали, что описанный алгоритм строит сжатое суффиксное дерево слова 𝑌 длины 𝑚 за 𝑂(𝑚) действий. После этого для любого слова 𝑋 длины 𝑛 можно за 𝑂(𝑛) действий выяснить, является ли 𝑋 подсловом слова 𝑌 .
    10.8.8. Как модифицировать алгоритм построения суффиксного де- рева, чтобы не только узнавать, является ли данное слово 𝑋 подсловом слова 𝑌 , но и (если является) указывать место, где оно встречается
    (одно из таких мест, если их несколько)? Время построения должно оставаться 𝑂(|𝑌 |), время поиска подслова | 𝑂(|𝑋|).
    Решение. Каждая вершина сжатого суффиксного дерева соответ- ствует некоторому подслову слова 𝑌 ; в момент, когда эта вершина была добавлена в дерево, известно, в каком месте есть такое подслово, и мож- но записать в вершине, где соответствующее подслово кончается.
    

    208 10. Сопоставление с образцом
    10.8.9. Как модифицировать этот алгоритм, чтобы для каждого подслова можно было бы указывать его первое (самое левое) вхожде- ние?
    [Указание. При возникновении новой вершины на ребре нужно брать её первое вхождение (информация о котором есть на конце ребра), а не второе, только что обнаруженное.]
    
    10.8.10. Как модифицировать этот алгоритм, чтобы для каждого подслова можно было бы указывать его последнее (самое правое) вхо- ждение?
    [Указание. Если при каждом проходе корректировать информацию вдоль пути, это будет долго; быстрее построить дерево, затем вновь его обойти и для каждой вершины вычислить момент последнего появления соответствующего подслова.]
    
    10.8.11. Как использовать сжатое суффиксное дерево, чтобы для данного слова 𝑌 за время 𝑂(|𝑌 |) найти самое длинное подслово, которое входит в 𝑌 более одного раза?
    Решение. Такое подслово является внутренней вершиной сжатого суффиксного дерева, поэтому достаточно из всех его вершин взять ту, которой соответствует самое длинное слово. Для этого достаточ- но обойти все его вершины (длину можно вычислять по мере обхода,
    складывая длины пометок на рёбрах).
    
    На практике можно использовать также и другой способ нахожде- ния самого длинного подслова, входящего дважды, | так называемый массив суффиксов. А именно, будем рассматривать число 𝑖 как «код»
    конца слова, начинающего с 𝑖-й буквы. Введём на кодах порядок, со- ответствующий лексикографическому (словарному) порядку на словах:
    код 𝑖 предшествует коду 𝑗, если конец слова, начинающийся с 𝑖, в лекси- кографическом порядке идёт раньше конца слова, начинающегося с 𝑗.
    После этого отсортируем коды в соответствии с этим порядком, по- лучив некоторую перестановку массива 1, 2, 3, . . . , 𝑚 (где 𝑚 | длина исходного слова 𝑌 ). Если какое-то слово 𝑋 входит в слово 𝑌 дважды,
    то оно является началом двух концов слова 𝑌 . При этом эти концы можно выбрать соседними в лексикографическом порядке, поскольку все промежуточные слова тоже начинаются на 𝑋. Значит, достаточно для всех соседних концов посмотреть, сколько начальных букв у них совпадает, и взять максимум.
    Этот способ требует меньше памяти (нам нужен лишь один массив из целых чисел той же длины, что исходное слово), но может требо- вать большого времени: во-первых, сортировка сама по себе требует

    10.8. Суффиксные деревья
    209
    порядка 𝑚 log 𝑚 сравнений, во-вторых, каждое сравнение может длить- ся долго, если совпадающий кусок большой. Но в случаях, когда длин- ных совпадающих кусков мало, такой алгоритм работает неплохо.
    10.8.12. Примените один из таких алгоритмов к любимой книге и объясните результат.
    [Указание. Длинные повторяющиеся куски могут быть художествен- ным приёмом (как в известном стишке про дом, который построил
    Джек) или следствием забывчивости автора. Для современных авто- ров возможно также неумеренное использование функций вырезания и вставки (заливки текста в мышь и выливания из мыши, если исполь- зовать графический интерфейс) в текстовом редакторе.]
    

    11. АНАЛИЗ ИГР
    11.1. Примеры игр
    11.1.1. Двое играют в такую игру: на столе лежит 20 спичек; игра- ющие по очереди могут взять от 1 до 4 спичек; кто не может сделать хода (спичек не осталось) | проигрывает. Кто выигрывает при пра- вильной игре?
    Решение. Второй выигрывает, если будет дополнять ход первого до 5 спичек (если первый берёт одну, второй должен взять четыре и так далее). Тогда после четырёх раундов спичек не останется и первый проиграет.
    
    11.1.2. Кто выиграет | первый или второй | если спичек не 20,
    а 23?
    Решение. Первый: если он возьмёт три спички, то станет вторым в уже разобранной игре и потому сможет выиграть.
    
    Аналогично получается ответ и для произвольного числа спичек 𝑁:
    если 𝑁 кратно пяти, то выигрывает второй, а если нет, то первый.

    1   ...   9   10   11   12   13   14   15   16   ...   19


    написать администратору сайта