Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Demon's Souls |#13| Storm King
Demon's Souls |#11| Мaneater part 2
Demon's Souls |#10| Мaneater (part 1)
Demon's Souls |#9| Heart of surprises

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Различные авторы Весь текст 991.22 Kb

Программирование в теоремах и задачах

Предыдущая страница Следующая страница
1 ... 67 68 69 70 71 72 73  74 75 76 77 78 79 80 ... 85
является ли слово X длины n подсловом слова Y длины m. (Как  это
делать  с помощью специального разделителя #, описано выше.) При
этом число действий будет не более C*(n+m), и  используемая  па-
мять  тоже. Придумать, как обойтись памятью не более Cn (что мо-
жет быть существенно меньше, если искомый  образец  короткий,  а
слово, в котором его ищут - длинное).

     Решение.  Применяем  алгоритм КМП к слову A#B. При этом вы-
числение значений l[1],...,l[n] проводим для слова X длины  m  и
запоминаем  эти  значения. Дальше мы помним только значение l[i]
для текущего i - кроме него и кроме таблицы l[1]..l[n], нам  для
вычислений ничего не нужно.

     На практике слова X и Y могут не находиться подряд, поэтому
просмотр  слова  X и затем слова Y удобно оформить в виде разных
циклов. Это избавляет также от хлопот с разделителем.

     10.4.5. Написать соответствующий алгоритм (проверяющий, яв-
ляется ли слово X=x[1]..x[n] подсловом слова Y=y[1]..y[m]).

     Решение. Сначала вычисляем таблицу l[1]..l[n]  как  раньше.
Затем пишем такую программу:
     j:=0; len:=0
     {len - длина максимального начала слова X, одновременно
            являющегося концом слова y[1]..j[j]}
     while (len <> n) and (j <> m) do begin
     | while (x[len+1] <> y[j+1]) and (len > 0) do begin
     | | {начало оказалось неподходящим, применяем к нему n}
     | | len := l[len];
     | end;
     | {нашли подходящее или убедились в отсутствии}
     | if x[len+1] = y[j+1] do begin
     | | {x[1]..x[len] - самое длинное подходящее начало}
     | | len := len+1;
     | end else begin
     | | {подходящих нет}
     | | len := 0;
     | end;
     | i := i+1;
     end;
     {если len=n, слово X встретилось; иначе мы дошли до конца
        слова Y, так и не встретив X}

     10.5. Алгоритм Бойера - Мура

     Этот алгоритм делает то, что на первый взгляд  кажется  не-
возможным:  в  типичной  ситуации он читает лишь небольшую часть
всех букв слова, в котором ищется заданный образец. Как так  мо-
жет  быть? Идея проста. Пусть, например, мы ищем образец "abcd".
Посмотрим на четвертую букву слова: если, к примеру,  это  буква
"e",  то  нет  никакой необходимости читать первые три буквы. (В
самом деле, в образце буквы "e" нет, поэтому он  может  начаться
не раньше пятой буквы.)

     Мы приведем самую простой вариант этого алгоритма,  который
не  гарантирует быстрой работы во всех случаях. Пусть x[1]..x[n]
- образец, который надо искать. Для каждого символа s найдем са-
мое правое его вхождение в слово X, то есть  наибольшее  k,  при
котором x[k]=s. Эти сведения будем хранить в массиве pos[s]; ес-
ли  символ  s вовсе не встречается, то нам будет удобно положить
pos[s] = 0 (мы увидим дальше, почему).

     10.5.1. Как заполнить массив pos?

     Решение.
        положить все pos[s] равными 0
        for i:=1 to n do begin
          pos[x[i]]:=i;
        end;

В  процессе поиска мы будем хранить в переменной last номер буквы
в слове, против которой последняя буква образца. Вначале last = m
(длине образца), затем постепенно увеличивается.

     last:=m;
     {все предыдущие положения образца уже проверены}
     while last <= m do begin {слово не кончилось}
     | if x[m] <> y[last] then begin {последние буквы разные}
     | | last := last + (m - pos[y[last]]);
     | | {m - pos[y[last]]  - это минимальный сдвиг образца,
     | |    при котором напротив y[last] встанет такая же
     | |    буква в образце. Если такой буквы нет вообще,
     | |    то сдвигаем на всю длину образца}
     | end else begin
     | | если нынешнее положение подходит, т.е. если
     | | x[1]..x[m] = y[last-m+1]..y[last],
     | | то сообщить о совпадении;
     | | last := last+1;
     | end;
     end;

Знатоки рекомендуют проверку совпадения проводить справа налево,
т.е. начиная с последней буквы образца (в которой совпадение за-
ведомо есть). Можно также немного сэкономить, произведы  вычита-
ние заранее и храня не pos[s], а m-pos[s], т.е. число букв в об-
разце справа от последнего вхождения буквы s.

     Возможны разные модификации этого алгоритма. Например, мож-
но строку last:=last+1 заменить на last:=last+(m-u), где u - ко-
ордината второго справа вхождения буквы x[m]  в образец.

     10.5.2. Как проще всего учесть это в программе?

     Решение. При построении таблицы pos написать
        for i:=1 to n-1 do...
в основной программе вместо last:=last+1 написать
        last:= last+m-pos[y[last]];


     Приведенная нами упрощенный вариант алгоритма Бойера - Мура
в некоторых случаях требует существенно больше n действий (число
действий  порядка  mn),  проигрывая  алгоритму Кнута - Морриса -
Пратта.


     10.5.3. Привести пример ситуации, в которой образец не вхо-
дит в слово, но авлгоритму требуется порядка mn действий,  чтобы
это установить.

     Решение. Пусть образец имеет вид  baaa..aa,  а  само  слово
состоит  только  из  букв a. Тогда на каждом шаге несоответствие
выясняется лишь в последний момент.


     Настоящий (не упрощенный) алгоритм Бойера - Мура гарантиру-
ет, что число действий не првосходит C*(m+n) в худшем случае. Он
использует  идеи,  близкие  к  идеям алгоритма Кнута - Морриса -
Пратта. Представим себе, что мы сравнивали  образец  со  входным
словом, идя справа налево. При этом некоторый кусок Z (являющий-
ся  концом образца) совпал, а затем обнаружилось различие: перед
Z в образце стоит не то, что во входном слове. Что можно сказать
в этот момент о входном слове? В нем обнаружен фрагмент,  равный
Z,  а перед ним стоит не та буква, что в образце. Эта информация
может позволить сдвинуть образец на несколько позиций вправо без
риска пропустить его вхождение. Эти сдаиги следует вычислить за-
ранее для каждого конца Z нашего образца. Как  говорят  знатоки,
все  это  (вычисление  таблицы  сдвигов и использовани ее) можно
уложэить в C*(m+n) действий.

     10.6. Алгоритм Рабина

     Этот алгоритм основан на простой идее. Представим себе, что
в слове длины m мы ищем образем длины n. Вырежем окошечко разме-
ра  n  и будем двигать его по входному слову. Нас интересует, не
совпадает ли слово в окошечке с заданным образцом. Сравнивать по
буквам долго. Вместо этого фиксируем некоторую функцию на словах
длины n. Если значения этой функции на слове в окошечке и на об-
разце различны, то совпадения нет. Только если значения одинако-
вы, нужно проверять совпадение по буквам.

     Что мы выигрываем при таком подходе? Казалось бы, ничего  -
ведь  чтобы  вычислить значение функции на слове в окошечке, все
равно нужно прочесть все буквы этого слова. Так уж лучше их сра-
зу сравнить с образцом. Тем не менее выигрыш возможен, и вот  за
счет  чего.  При  сдвиге окошечка слово не меняется полностью, а
лишь добавляется буква в конце и убирается в начале. Хорошо  бы,
чтобы по этим данным можно было бы легко рассчитать, как меняет-
ся функция.

     10.6.1. Привести пример удобной для вычисления функции.

     Решение. Заменим все буквы в слове и образце  их  номерами,
представляющими  собой целые числа. Тогда удобной функцией явля-
ется сумма цифр. (При сдвиге окошечка нужно добавить новое число
и вычесть пропавшее.)

     Для каждой функции существуют слова, к которым она примени-
ма плохо. Зато другая функция в этом случае может работать хоро-
шо. Возникает идея: надо запасти много функций и в начале работы
алгоритма выбирать из них случайную. (Тогда враг, желающий подга-
дить нашему алгоритму, не будет знать, с какой  именно  функцией
ему бороться.)

     10.6.2. Привести пример семейства удобных функций.

     Решение.  Выберем  некоторое  число  p (желательно простое,
смотри далее) и некоторый вычет x по модулю p. Каждое слово дли-
ны n будем рассматривать как последовательность целых чисел (за-
менив буквы кодами). Эти числа будем рассматривать как коэффици-
енты многочлена степени n-1 и вычислим значение этого многочлена
по модулю p в точке x. Это и будет  одна  из  функций  семейства
(для каждой пары p и x получается, таким образом, своя функция).
Сдвиг  окошка на 1 соответствует вычитанию старшего члена, умно-
жению на x и добавлению свободного члена.
     Следующее соображение говорит в пользу того, что совпадения
не слишком вероятны. Пусть число p фиксировано и к тому же прос-
тое,  а  X  и  Y  -  два различных слова длины n. Тогда им соот-
ветствуют различные многочлены (мы предполагаем, что  коды  всех
букв  различны  - это возможно при p, большем числа букв алфави-
та). Совпадение значений функции означает, что в точке x эти два
различных многочлена совпадают, то есть их разность обращается в
0. Разность есть многочлен степени n-1 и имеет не более n-1 кор-
ней. Таким образом, если n много меньше p, то случайному x  мало
шансов попасть в неудачную точку.


     10.7. Более сложные образцы и автоматы

     Мы можем искать не конкретно слово,  а  подслова  заданного
вида.  Например, можно искать слова вида a?b, где вместо ? может
стоять любая буква (иными словами, нас  интересует  буква  b  на
расстоянии 2 после буквы a).

     10.7.1  Указать  конечный  автомат, проверяющий, есть ли во
входном слове фрагмент вида a?b.

     Решение.  Читая  слово, следует помнить, есть ли буква a на
последнем месте и на предпоследнем - пока  не  встретим  искомый
фрагмент. Получаем такой автомат:

    Старое состояние    Очередная буква   Новое состояние

       00                     a                 01
       00                  не a                 01
       01                     a                 11
       01                  не a                 10
       10                     a                 01
       10                     b                 найдено
       10                не a и не b            00
       11                     a                 11
       11                     b                 найдено
       11                не a и не b            10


     Другой стандартный знак в образце - это звездочка  (*),  на
место  которой может быть подставлено любое слово. Например, об-
разец ab*cd означает, что мы ищем подслово ab, за которым следу-
ет что угодно, а затем (на любом расстоянии) следует cd.

     10.7.2. Указать конечный автомат, проверяющий, есть  ли  во
входном слове образец ab*cd (в описанном только что смысле).

     Решение.

    Старое состояние    Очередная буква   Новое состояние

       нач                    a                 a
       нач                 не a                 нач
        a                     b                 ab
        a                     a                 a
        a                  не a и не b          нач
        ab                    c                 abc
        ab                 не c                 ab
        abc                   d                 найдено
        abc                   c                 abc
        abc                не с и не d          ab

     Еще один вид поиска - это поиск любого из слово  некоторого
списка.

     10.7.3.  Дан  список  слов X[1],...,X[k] и слово Y. Опреде-
лить, входит ли хотя бы одно из слов X[i] в слово Y (как подсло-
во). Количество действий не должно превосходить константы, умно-
женной на суммарную длину всех слов (из списка и того, в котором
происходит поиск).

     Решение. Очевидный способ состоит в том, чтобы каждое слово
из  списка проверять отдельно (с помощью одного из рассмотренных
алгоритмов). Однако при этом мы не укладываемся в заданное число
действий (из-за умножения k на длину слова Y).

     Посмотрим на дело с  другой  стороны.  Каждому  образцу  из
списка соответствует конечный автомат с некоторым множество сос-
тояний. Их можно объединить в один автомат, множеством состояний
которого будет произведение множеств состояний всех тех  автома-
тов.  Это  -  очень  большое  множество.  Однако  на  самом деле
большинство его элементов недоступны  (не  могут  появиться  при
Предыдущая страница Следующая страница
1 ... 67 68 69 70 71 72 73  74 75 76 77 78 79 80 ... 85
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 
Комментарии (1)

Реклама