- •Інформатика
- •Необчислювальні алгоритмы
- •Від автора
- •Створення алгоритму
- •Налагодження алгоритму
- •Допоміжні задачі
- •Поняття систем числення
- •Числова інформація Цілі числа
- •Дійсні числа
- •Текстова інформація Символи
- •Дерево. Бінарне дерево
- •If to nil then with t* do begin
- •Бінарний пошук Пошук діленням навпіл
- •Рекурсивний бінарний пошук
- •Пошук у рядку
- •Скінченні автомати Основні поняття
- •Пошук у мережі
- •Прямі методи сортування Сортування вибором
- •Сортування обміном
- •Шейкерне сортування
- •Сортування методом Шелла
- •Швидке сортування
- •Метод прямого злиття
- •Метод природного злиття
- •Сортування підрахунком
- •Цифрове сортування
- •Література
- •61012, М. Харків, вул. Енгельса, 11.
Скінченні автомати Основні поняття
Теорія автоматів - це один із розділів інформатики. Якщо бути точнішими, то теорія автоматів належить до теоретично! інформатики і вивчає методи, за допомогою яких можна на ос-нові моделей логічного типу вивчати процеси, що протікають у різних пристроях під час обчислень.
Почнемо з уведення загальних понять теорії автоматів.
У широкому тлумаченні автоматами називають пристрої, які виконують процеси приймання, перетворення та передачі енергії, матеріалів або інформації відповідно із закладеними у них програмами.
В інформатиці під автоматом розуміють перетворювач ін-формації, для якого задаються множини вхідних сигналів А, внутрішніх станів Q, вихідних сигналів V, а також дві функції: функція переходів станів 6 та функція виходів X.
Схематично роботу автомата можна зобразити так (мал. 42):
Q |
w |
^ |
Мал. 42
I ще нам треба ввести такі два поняття: скінченний вхідний алфавіт, який насправді є множиною елементів, над якими може виконуватися перетворення даним автоматом, напри-клад множина натуральних чисел або латинські літери, та слово - підмножина алфавіту, над якою безпосередньо і відбу-вається перетворення, тобто яку обробляє автомат у даному
127
конкретному випадку, налриклад задане натуральне число N або деякий текст латинськими літерами. Іншими словами, «слово» - це вхідна інформація.
Якщо введет нові поняття здаються вам складними, то на-справді це зовсім не так. Розберемося з ними детальніше, і все стане напрочуд зрозумілим. Згодом навіть виявиться, що ми вже користувалися алгоритмами, які реалізують скінченні автомати. Термін «скінченний» пояснимо, коли детальніше на прикладах ознайомимося з поняттям автомата.
Для кращого розуміння, почнемо з формулювання задачі, розв'язування якої ми розглянемо в термінах автомата. Нехай задана послідовність натуральних чисел а[і], і - 1, 2, ..., п, у якій треба всі числа, що перевищують 7, замінити на 7.
Завданням автомата є обробка за певною програмою інфор-мації, яка подається йому у вигляді «слова». У нашому випадку таким «словом» є задана послідовність дійсних чисел. «Слово» складається з окремих елементів а - заданих натуральних чисел, що входять до множини вхідних сигналів А, тобто до множини натуральних чисел. Функція переходів визначає, в який стан q' із множини можливих станів Q перейде перетво-рювач, якщо він перебувае у стані q i на вхід надійшов сигнал а: б(<у, а) = q'. У термінах нашої задачі це означає, що коли на деякому кроці в послідовності значень а[і] буде оброблятися число, значения якого перевищує 7, то воно буде замінено на 7. Тобто при обробці числа а[і] > 7 спрацює функція переходу 6: а[і] :- 7 і перетворить число, яке більше за 7 (вхідний сигнал), на число 7. Зауважимо, що q' також входить до множини Q.
Функція виходів у результат! виконання функції перетво-рення показує, який при цьому виробляється вихідний сигнал v, що належить множит вихідних сигналів V: k(q, а) = v. У нашому прикладі вихідних сигналів буде два: один - про-довження введения дійсних чисел у випадку, коли і < п, і завершения введения, коли і > п. Перший позначимо, наприклад, О, другий - I.
Стандартним описом скінченного автомата є таблиця, яку називають таблицею перетворень.
Отже, представимо описаний скінченний автомат, що реа-лізує поставлену задачу у вигляді такої таблиці переходів (табл. 11): у рядках розмістимо можливі стани виконання алгоритму - перехід до наступного елемента заданої послідов-ності та заміну поточного елемента на 7; у стовпчиках — усі можливі значения елементів послідовності. На перетині відпо-відних рядків і стовпчиків розміщене посилання на функцію переходу, яку описує даний елемент таблиці. Наприклад, якщо поточний елемент послідовності дорівнює 2, то переходимо до розгляду наступного елемента, якщо ж 8, то спочатку робимо
128
заміну його значения на 7, а потім знову переходимо до розгля-ду наступного елемента. Алгоритм завершиться, коли припиши-ься введения елементів послідовності.
Таблицяіі
^~~~\^^ Вхіда, Стан q, \^^ |
1 |
2 |
... |
6 |
7 |
8 |
... |
?,(і := і + 1) |
9і |
9і |
Яг |
9і |
9і |
92 |
Яг |
92(а[і]:=7) |
9і |
9і |
Ь |
аі |
9і |
?і |
?і |
Тепер можна визначитися щодо назви «скінченні автомата». Справа в тому, що у нашому випадку всі множини сигна-лів А та V і внутрішніх станів Q дискретні, тобто порційні, а їх елемента ізольовані один від одного: послідовність вхідних дійс-них чисел (а[ї\, і - 1, 2, ..., п), перетворення значень деяких із них за умовою задачі (якщо а[ї] > 7, тоді а[і]:- 7), завершения виконання задачі (і > п). Дискретною є і послідовність момен-тів, у які надходять вхідні сигнали, отримуються вихідні та змінюються внутрішні стани: перехід до введения наступного значения а[ї\ відбувається лише після завершения обробки попереднього.
Будемо говорити, що якщо множини сигналів і станів скін-ченні, то автомат, обо перетворювач, називається скінченним.
Наведемо означения скінченного автомата в термінах алго-ритмізапії, оскільки саме цей розділ інформатики знаходиться в полі нашого зору і саме методами побудови алгоритмів ми займаємося.
Скінченним автоматом будемо називати алгоритм, у якому передбачено обробку скінчешюї кількості всіх можливих ситуацій та переходу вхідної інформації за скінченну кіль-кість кроків із заданого початкового стану в кінцевий.
Тепер, коли поняття скінченного автомата стало досить про-зорим, можна навести чимало прикладів із тих задач, які ми раніше розв'язували, і тепер сформулювати їх у нових термі-нах. Наприклад, задача, яка передбачає введения недодатних значень для змінних, що можуть набувати лише натуральних чисел; контроль за перемиканням клавіатури на ту чи іншу абетку при введенні текстової інформації тощо.
Розглянемо ще кілька задач, алгоритми яких можна пред-ставити у вигляді скінченних автоматів.
■ ) Ыфораттщгя 9 l»11 і
129
Нехай треба визначити правильність розміщення круглих дужок у заданій математичній формулі. Для спрощення вва-жатимемо, що вхідна інформадія задається у вигляді тексту, який містить натуральні числа, знаки арифметичних операцій (+, -, •, /) і відкриті та закриті круглі дужки.
Алгоритм визначення коректності розставлених круглих дужок в арифметичному виразі полягає в покроковому збіль-шенні лічильника k у випадку «(» та його зменшення у випадку «)» . Якщо протягом виконання алгоритму виконається умо-ва k < О, то це означатиме, що порушено баланс відкритих та закритих дужок і далі виконання алгоритму не мае змісту. Якщо перегляд вхідної інформації завершено, то ознакою ко-ректності розміщення круглих дужок в арифметичному виразі є виконання умови k »= 0.
Які ситуації нам потрібно обробляти, тобто які внутрішні стани можуть бути? Перше - це перехід до наступного символу у випадку, коли він не є дужкою, та перевірка завершеності введения інформації. Друге - підрахунок відкритих круглих дужок, тобто збільшення лічильника ft на 1. Третє — підра-хунок закритих круглих дужок, тобто зменшення лічильника k на 1. Четверте — перевірка співвідношення k < 0, тобто кон-тролювання ситуації, коли закритих дужок стане більше, ніж відкритих. П'яте - завершения роботи автомата. Робота такого автомата представлена у вигляді таблиці 12.
Таблиц* 12
^-. Вхід а, Стенд, ^--^ |
0 |
1 |
... |
9 |
+ |
- |
|
/ |
( |
) |
q, (перехід до наступного символу при і< п) |
4v 0/1 |
7,. o/i |
9ii 0/1 |
4v 0/1 |
9,. 0/1 |
9v 0/1 |
7i' o/i |
7,. 0/1 |
72. 0/1 |
78. 0/1 |
д., (збільшення лі- чильника к для «(» на 1) |
?4.0 |
<74.0 |
7,-0 |
7,-0 |
g4.o |
<7,. 0 |
7..0 |
7„0 |
7..0 |
74>0 |
<73 (зменшення ЛІ- чильника k для «)» на 1) |
?4.0 |
?4'0 |
g4,o |
«/4,0 |
94.° |
?4.0 |
?4.0 |
74-0 |
74.0 |
74-0 |
qt (Error: k < 0) |
4v 1/0 |
4v 1/0 |
<7,. 1/0 |
4v I/O |
1v 1/0 |
Si |
4v I/O |
7p I/O |
7,-1/0 |
7,. I/O |
qb (Stop: к - 0) |
Я,Л |
?5. 1 |
<75. 1 |
9e.l |
Ї..1 |
75.1 |
95.1 |
75.1 |
75,1 |
75.1 |
130
У комірках на перетині відповідних станів та елементів вхідної інформації будемо вказувати дві величины: значения функції переходу 6(9,, а.) та через кому значения функції вихо-ду X(q., cif) (табл. 12).
Значениям функції переходу 6 може бути одне з можливих значень, указаних у переліку станів qr Значениям функції выходу К може бути 0 (робота автомата продовжуеться) та 1 (робота автомата припиняється).
Для зручності значения функції виходу будемо вказувати так. Якщо результат цієї функції однозначний, то будемо вказувати одне це значения, якщо ж значень може бути два, за-лежно від виконання умови, яка аналізується, то будемо вказувати два значения: перше - для випадку виконання умови, друге - для випадку ЇЇ невиконання. Наприклад, qv 0/1 означав перехід до стану qi зі значениям функції виходу 0 у разі виконання умови, що аналізується в цьому стані (це означав, що робота автомата продовжуеться), та 1 у разі невиконання умови (робота автомата припиняеться).
Отже, ми представили алгоритм, описаний вище, в іншій формі - у вигляді таблиці переходів скінченного автомата.
Змінимо попередню задачу і будемо контролювати корект-ність написания імен функцій, використання яких передбаче-но умовою задачі. Для спрощення запису таблиці переходів створимо скінченний автомат лише для функції синуса і будемо вважати, що символи «s», «i» та «n» використовуються лише у выклику вказаної функції. Побудуємо таблицю переходів для цієї задачі (табл. 13).
Таблиця 13
^\^ Вхід а, Станд, ^--^ |
0 |
1 |
... |
9 |
+ |
- |
|
s |
І |
п |
ql (перехід до на- ступного символу при і < N) |
9,> 0/1 |
9,. 0/1 |
0/1 |
4v 0/1 |
9v 0/1 |
9p 0/1 |
9i> 0/1 |
Яг> 0/1 |
Я,Л |
9,.1 |
q2 (иерехід до на- стушюго символу при і < N і перєвірка символу «і») |
Яі'1 |
92. 1 |
Яг'1 |
92. 1 |
Яг< X |
qv 1 |
ЯгЛ |
ЯгЛ |
я3> 0/1 |
7,.1 |
q3 (перехід до на- ступного символу при j < N і перевірка символу «11») |
я3Л |
98. 1 |
g3.i |
9з»1 |
9,.l |
Ч,Л |
я3Л |
Я3Л |
9а»1 |
9і-0/1 |
R
131
Мабуть, варто пояснити деякі значения функцій переходів у наведеній таблиці.
Для стану gt характерно те, що введения будь-якого символу, крім «s», «і» та *п», призводить до переходу до цього самого стану д, із значениям функції виходу 0, коли введения інформації ще не завершено, та 1, коли завершено. У випадку введения символу «s» перехід відбувається лише до стану q2 із значениям функції виходу 0, тобто введения продовжується далі, або ж 1, коли цей символ є останнім. У випадку введения символів «і» та «п» виробляеться значения функції виходу 1, і робота автомата припиняється, оскільки за умовою задачі ці символ и не можуть передувати символу «s».
Для стану q2 введения будь-якого символу, крім «і», є помилковим, тому значения функції виходу дорівнює 1 і перехід до іншого стану не мае ніякого значення, а при введенні символу «і» перехід відбувається до стану q3 відповідно із значениям функції виходу 0, коли введения символів продовжується, і 1, коли припиняється.
Стан q3 передбачає перехід до стану g, лише при введенні символу «п» відповідно із значениям функції переходу 0/1, а в усіх інших випадках функщя виходу дае значення 1, тобто будь-яке значення символу, крім «п», є помилковим, і робота автомата припиняється.
Як і в попередньому прикладі за такою таблицею переходів нескладно розробити алгоритм, що контролює правильність за-пису функції синус в математичному виразі.
Спробуємо відповісти на запитання: коли варто формулю-вати алгоритми у термінах скінченних автоматів? Зрозуміло, що коли алгоритм є досить прозорим і в ньому небагато пере-творень, то у створенні таблиці переходів немає потреби. Якщо ж умова задачі передбачає багато перетворєнь або ж ці пере-творення є досить склад ними, залежними одні від одних, то зручніше побудувати таблицю переходів і при цьому реалізація алгоритму стане набагато прозорішою.
А зрештою, використання скінченних автоматів треба роз-глядати як один із підходів до побудови алгоритмів.
Слушним є запитання, чому елементи теорії автоматів ми розглядаємо в розділі пошукових алгоритмів. Справа в тому, що деякі алгоритми, які можна представити у вигляді таблиці переходів, і є пошуковими. Адже задачі, які ми розглянули, підтверджують це.
Запитання для самоконтролю
-
Які питания інформатики вирішує теорія автоматів?
-
Як тлумачиться в інформатиці поняття «автомат»?
-
Які основні поняття входять до формування поняття «автомат»?
132
-
Зобразіть схематично роботу автомата.
-
Чому автомати, які розглядаються в інформатиці, називаються скінченними?
-
Дайте означения сличенного автомата в термінах алгоритмізації.
-
Наведіть приклад задачі та сформулюйте її в термінах теорії ав-томатів. Побудуйте для неї таблицю переходів і функції виходу.
-
Обґрунтуйте значения таблиці переходів у наведеному в пункті 7 прикладі.
-
Коли варто формулювати алгоритми у термінах скінченних автомате?
Завдання
-
Реалізувати у вигляді алгоритму побудований скінченний автомат для задачі заміни на 7 усіх значень, що перевищують 7 у заданій послідовності натуральних чисел а[і], I = 1, 2,..., п.
-
Протестувати алгоритм, реалізований у завданні 1.
-
Реалізувати у вигляді алгоритму побудований скінченний автомат для задачі перевірки правильності розставлення круг-лих дужок в математичному виразі.
-
Протестувати алгоритм, реалізований у завданні 3.
-
Реалізувати у вигляді алгоритму побудований скінченний автомат для задачі перевірки правильності визначення імені функції синус в математичному виразі.
-
Протестувати алгоритм, реалізований у завданні 5.
Пошук підрядків за допомогою скінченних автоматів.
КМП-пошук
Цей метод уперше описано Д. Кнутом, Д. Морісом і В. Прат-том у 1970 р. Його особливістю є те, що він базується на ство-ренні функції перетворень.
Сформулюємо спочатку задачу, яку будемо розв'язувати. Нехай треба знайти перше входження підрядка х, який скла-даеться з т символів, у рядок s, у якому міститься п символів {т < п).
Подібну задачу ми вже розглядали, але ЇЇ розв'язування зво-дилося до лінійного пошуку, тобто ми покроково зміщували підрядок х у рядку s, перевіряючи збігання їх символів. Такий метод мае право на існування, але він не є досить ефективним, оскільки вимагає п ■ т порівнянь збігання символів.
Розглянемо інший підхід до пошуку розв'язання поставлено! задачі.
Почнемо порівнювати символ и підрядка х - х[1] ... х[т] із символами рядка s - s[l]... s[n] із початку. Нехай s[l] - х[1],..., «[/-!] = хЦ ~ 1]. s[j] * x\j], де / < т (мал. 43).
133
Ііадалі на малюыках ті елементи підрядка х і рядка s, що збі-гаються, будемо позначати світло-сірим кольором, а ті, що не збігаються, — темно-сірим.
s[l] |
e[i] |
... |
e[y-l] |
*w |
|
s[ml |
... |
8[д] |
41] |
x[l] |
... |
«0-1] |
*M |
... |
x[m] |
|
|
Мал. 43
Зрозуміло, що підрядок не входить у рядок із першої пози-ції, оскільки відбулося його незбігання у ;'-й позиції. Можна, звичаино, спробувати почати перевірку з другої за порядком позиції у рядку, як це робилося раніше при лінійному пошуку, але виявляеться, так діяти зовсім не обов'язково.
Розглянемо такий приклад: х = 'ababb' i s = 'ababababbab1 (мал. 44).
123456789 10 11
a |
b |
a |
b |
a |
b |
a |
b |
b |
a |
b |
a |
b |
a |
b |
Ь |
|
|
|
|
|
|
12 3 4 5
Мал. 44
3 малюнка видно, що після того як виявилося, що s[5] = 'а' * * 'Ь' = х[5], є сенс починати наступну перевірку лише з s[3], ос-кільки саме там є наступне входження початку підрядка х. Можна також помітити, що символами s[3]s[4] - 'ab' водночас закінчується й починається частина тдрядка лг[1]л:[2]д:[3]д:[4]-Тому можна також казати, що наступне входження підрядка х можливе, коли д:[1]х[2] займуть місце дг[3]х[4] (мал. 45). Таким чином, підрядок «зсунеться» відносно рядка відразу на дві по-зиції'!
123456789 10 11
а |
Ь |
а |
Ь |
а |
Ъ |
а |
Ь |
ъ |
а |
Ъ |
а |
Ъ |
а |
b |
Ъ |
|
|
|
|
|
|
|
|
а |
ь |
а |
Ъ |
Ь |
|
|
|
. |
12 3 4 5
Мал. 45
134
Тепер виявляється, що s[7] * х[5] і підрядок х можна знову зсунути відразу на дві позиції, щоб х[1]х[2] знову зайняли міс-це д:[3]дг[4], збігаючись при цьому з s[5]s[6] (мал. 46).
123456789 10 11
а |
b |
a |
b |
a |
b |
a |
b |
b |
a |
b |
а |
b |
a |
b |
b |
|
|
|
|
|
|
|
|
a |
b |
a |
b |
b |
|
|
|
|
|
|
|
|
a |
b |
a |
b |
b |
|
|
12 3 4 5
Мал. 46
Тепер s[7] = x[3], s[8] ■ x[4], s[9] = x[5] i входження, почи-наючи з позиції 5, знайдено.
Спробуємо узагальнити помічені особливості при відшукан-ні позиції зміщення підрядка х у рядку s.
Отже, нехай перевіряється входження зразка від позиції і — j, х[1] ... х[і] = s[i — ;']... s[i — 1], a x\j + 1] не збігається з чер-говим символом рядка s[i] (мал. 47).
s[l] |
... |
s[i-j] |
... |
S[/-1] |
s\i] |
s[l+l] |
... |
|
|
x[l] |
... |
«M |
x[j+l] |
xU+2] |
... |
Мал. 47
Виділимо i розглянемо окремо ту частину рядка s і цідряд-ка х, які збіглися (мал. 48).
Ф-Л |
... |
ep-1] |
41] |
... |
АП |
Мал. 48
Нагадаємо, що в розглянутому вище прикладі х = 'ababb' і s = 'ababababbab' ми намагалися відшукати найоптимальніший зсув нашого підрядка у рядку і такий зсув знайшли, змістивши на другому кроці наш підрядок х зразу на дві позиції у рядку s. Таким чином, ми помітили, що при такому зсуві перші два еле-менти підрядка збігаються з кінцем тієї її частини, що збіглася
135
за значениями з елементами рядка (див. мал. 45): х[1]д:[2] ■ = х[3]*[4] ('ab' - 'ab').
Узагальнюючи наведений приклад, можна дійти висновку, лцо в загальному випадку, коли збіглися частини шдрядка х[1] ... x\j] iрядкаs[i — /]... s[i - 1], требавідшукатитакий най-довший початок х[1]... x[k] підрядка, який водночас є і кінцем фрагмента підрядка х[1]... x[f], що у цій частині з деякої пози-ції збігається і з фрагментом заданого рядка s (мал. 49).
s[i-j] |
... |
s[i-j-k+l] |
... |
s[i~k+l] |
... |
s[i-l] |
х[1] |
... |
x[k] |
... |
xV-k+1] |
... |
*Ш |
|
|
|
|
x[l] |
... |
x[k] |
Мал. 49
Це можна пояснити так:
- по-перше, зсув підрядка по рядку може відбуватися лише в межах тієї частини х, що збіглася, тобто у проміжку від 1-го до у-го елемента;
- по-друге, при такому зсуві деякий початок підрядка х (перші k елементів) накладаємо на таку саму кількість його ос- танніх j елементів, і вони при цьому повинні збігатися.
Шдіб'ємо підсумок наведених вище міркувань: перехід від перевіреного початку підрядка довжини ;' до перевіреного початку довжини k (1 < k < j) означав зсув підрядка відносно рядка s відразу на jI — k позицій. Але на меншу кількість позицій зсувати підрядок немає сенсу, оскільки х[1]... х[Щ - це найдов-ший початок підрядка х, що збігається з кінцем фрагмента рядка s[l] ... s[i - 1].
Продовжимо наші міркування далі. Збігання елементів х[1] ... x[k] з елементами s[i - j - к + 1] ... s[i - 1] не припиняє процес порівняння далі, оскільки елементи підрядка х ще не вичерпано. А саме, якщо x[k + 1] - s[i], то можна продовжува-ти порівняння від символу s[i + 1]. Якщо x[k + 1] X s[i], то треба відшукати найдовший початок х[1] ... x[AJ підрядка, що збі-гається з кінцем х[1] ... x[k] (і з кінцем s[l]... s[i - 1]), і порів-няти x[kx + 1] із s[i]. Описаний алгоритм потрібно застосовува-ти далі аналогічним чином.
Наприклад, якщо s - 'abababc', ал» 'ababc', то за спроби «прикласти» підрядок, починаючи з першого символу рядка, маемо х[1] - s[l], х[2] - в[2], х[3] = в[3], х[4] = s[4], х[5] * s[5], тобто j = 4. Відповідним значениям k буде 2, оскільки 'ab' є най-довшим початком рядка 'abab', що є водночас його кінцем. Звідси випливає, що немає сенсу пробувати «прикласти» під-
136
рядок до рядка, починаючи з його другої позиції, а слід «пересунута» його відразу на у' — k = 2 позиції. При цьому гаран-тується рівність х[1] ... x[k] i s[i — k] ... s[i - 1], тобто назад від позиції s[i] у рядку можна не повертатися.
Отже, можна зробити висновок, що для заданого підрядка х існує строго визначена залежність і вона полягає в такому: для кожної позиції у підрядка відома найбільша довжина f(j) < j такого початку підрядка x[l] ... x[f(j)], що збігається з кінцем х[1] ... x{j].
Оскільки підрядок х задається на самому початку алгоритму пошуку і не змінюється протягом виконання пошуково-го алгоритму, то можна для кожної позиції у (1 < у < т) заданого підрядка визначити такі значения k (1 < k < у), для яких х[1] ... x[k] збігається з x{j - k + 1]... x\J]. Значения k i буде ca-ме тим зсувом, який повністю задовольняє оптимальні вимоги пошуку входження підрядка х у рядок s. Значения функції f(j) = k для l<ft<y<mie фактично функцією переходу при кожному конкретному стані розгляду фрагмента підрядка д:[1] ... x[j], де 1 <у'< т.
Перейдемо до алгоритму побудови функції переходу f(j), яка виражає довжину такого найдовшого початку рядка х[і]... x[j], що є водночас його кінцем. У літературі така функція ще нази-вається функцією відступів для методу КМП-пошуку.
Очевидно, що Д1) = 0. Далі міркуємо так. Нехай усі значения /(1),..., f(j - 1) уже обчислено, причому ВІДОМО, що f(j -l) = k, тобто у випадку збігання х[1] ... x[j - 1] із s[i - у - 1] ... s[i - 2] наступний зсув у рядку треба зробити на у - k елементів віднос-но s[i — j — 1] (мал. 50).
"p-jrij |
... |
s[i-j-k] |
... |
s[i-k] |
... |
s[i-2] |
x[l] |
... |
x[k] |
... |
x[j-k] |
... |
x\j~r\ |
|
|
|
|
x[l] |
... |
x[k] |
Мал. 50
Розглянемо тепер, яким може бута значения /(у). Нага-даемо, як треба тлумачити значения /(у). По-перше, це означав, що ми продовжили розгляд фрагмента підрядка від його початку ще на один елемент: х[1]... x\j]. Але якщо розглянути попе-редне «прикладання» х[1] ... x[k] до x\j - k] ... x[j - 1], яке ми визнали таким, що збігається, то тепер треба провести порів-няння наступних двох елементів: x[j] i x[k + 1] (мал. 51).
137
s[i-j-l] |
... |
s[i-j-k] |
s[i-j-k+l] |
... |
s[i-k] |
... |
Sp-2] |
s[i-l] |
x[l] |
... |
x[k] |
x[k] |
... |
xV-k] |
... |
x[j-l] |
m |
|
|
|
|
|
x[l] |
... |
x[k] |
x[k+l] |
Мал. 51
Тут можливі два варіанти.
Якщо x[j] = x[k + 1], то кінець рядка х[1] ... x[j - l]x\j] збі-гається з його початком довжини ft + 1, тому логічно, що f(j) =
= Г0-1) + 1 = л + 1.
Якщо ж x[j] =■= x[k + 1], то треба спробувати підібрати інший такий фрагмент х[1]... x[ftl], що х[1]... x[ftj = x[j;- ft, + 1]... x\j] при ft, < ft. Як же знайти значения ft,? Давайте перелічимо ті факти, які нам на даний час відомі:
-
фрагмент підрядка д:[1] ... x[k] збігається з його ж фрагментом x\j - ft]... x[j - 1];
-
x[k + 1] * x[j], тобто збігання наступних двох елементів тих фрагментів підрядка, які розглядаються, відсутнє.
Це означає, що якщо фрагмент підрядка х[1] ... x\j - 1] = = s[i - ft]... s[i - 2], x[j] * s[i - 1] i для цього випадку відоме значения ft для наступного зсуву підрядка по рядку, то відповідно виконується і таке твердження: д:[1]... x[k] = s[i - ft]... s[i - 2], x[k + 1] * s[i - 1] (див. мал. 49). Який висновок з цього можна зробити? А висновок такий, що знову маемо справу зі стартового ситуацією і переходимо до початку наших міркувань, ви-значивши j - 1 = ft (ft < j - 1). А що було зроблено для того, щоб визначити, на яку кількість символів у x[lj... x[k] треба змісти-тися, щоб деякий його початок збігався з його кінцем? Оскіль-ки ft < j - 1, то ця інформація вже відома і є значениям елемен-та масиву /(ft).
Отже, «наступним кандидатом у кінці» рядка х[1] ... ... x{j — l]x[j] є рядок х[1~\ ... x[f(k)]x[f(k) + 1], оскільки саме х[1] ... x[f(k)] є найдовшим кінцем х[1] ... x[k]. Якщо і він не придатний, то наступним є д:[1]... x[f{f(k)) + 1] тощо. Таким чином, ми або знайдемо початок довжинир такий, що х[1]... х[р] є кінцем дг[1]... x\j], і тоді f(j) = p, або не знайдемо, і тоді f(j) = 0.
Описаний алгоритм, як бачимо, є рекурентним. Запишемо його у вигляді тексту Pascal-програми, де значения функції f будемо записувати у масив:
f[1]:=0; forj := 2ton do begin
k:=f[j-1];
while (x[j] <> x[k + 1]) and (k > 0) do k := f[k];
138
If (x[j]<>x[k+1])and(k = 0) thenffj] :=0 elsef[j]:=k+1; end;
Однак рекурентність досить часто можна замінити рекур-сією. Тому слід запропонувати й такий варіаит цього самого алгоритму обчислення значень функції переходів для заданого підрядка х:
procedure КМР (k: word); begin if к = О
then f[j] := О elseifx[f[k-1] + 1] = x[j]
then fШ := f [k - 1] + 1 elseKMP(f[k]); end;
Побудова функції переходів здійснюється таким фрагментом алгоритму:
f[1]:=0;
forj := 2 torn do
KMP(j);
Побудова функції переходів за доиомогою рекурсивного алгоритму може виглядати і так:
procedure КМР; var k, j: word; begin
f[1]:=0; forj := 2 to n do begin k:=f[j-1];
while (x[j] о x[k + 1]) and (k > 0) do к := f[k]; if (x[j]ox[k+ 1])and(k = 0) then f[j] := 0 elsef[j] :=k+ 1; end; end;
У цьому разі виклик процедури буде найпростішим: КМР;
Оцінимо загальну кількість порівнянь символів, виконува-них за цим алгоритмом. Позначимо через w(j) кількість вико-нань тіла циклу для відповідного значения ;' = 2, ..., т. Неваж-ко побачити, що кожне виконання тіла циклу while зменшує
139
значения k не менше як на 1. Звідси f[f\ <= f[j - 1] - w(j) + 1, тоб-то w(j) <- f[j - 1] - /[/] + 1. Тоді:
ш(2) + ш(3) + ... + ш(/п) <= Я1] - Я2] + 1 + Я2] - ЯЗ] + 1 + .« + + Я"і - 1] - Л>] + 1 = Я1] + т-1 <-j»-l.
Для кожного у відбувається порівнянь символів на 2 більше, ніж виконань тіла циклу: одне додаткове порівняння під час обчислення умови в заголовку циклу і одне - в умовному опера-торі. Звідси загальна кількість порівнянь символів не більша за 3(т - 1), тобто пропорційна т. Таким чином, загальну кіль-кість порівнянь символів під час побудови функції відступів можна оцінити як 0{т).
Тепер, нарешті, наведемо алгоритм пошуку входжень порядка х у рядок s. Причому будемо шукати всі можливі входження.
Нехай через t позначимо номер поточної позиції в рядку, j -номер поточної позиції у підрядку. їх початкові значения до-рівнюють 1.
Далі, поки t <- п, виконуємо наступні дії. Порівнюємо сим-воли x{j] і s[t]. Якщо вони рівні, то маємо входження х[1]... x[f\ у кінці рядка s[l]... s[t]. Якщо при цьому j = т., то можна пові-домити про входження підрядка х, почйнаючи з позиції t — j + 1, i приступати до пошуків наступного його входження, поклавши / = f(m). Якщо ж у < т, то переходимо до наступної пари символів, збільшивши t i у на 1. Якщо s[t] i x{j\ нерівні при j > 1, то міняємо значения у на f[j — 1] + 1, а при у = 1 — збіль-шуємо t на 1. Проте зміни у" не мають сенсу, коли t = п. Ось i все.
Наведемо описаний алгоритм також мовою Pascal:
i := 1; j := 1;flag :=true; while (i <= n) and flag do begin
ifx[j] = s[i] then if j = m then begin write(f_out, 'Text has found at the position: #'); writeln(f_out, i - j + 1); flag := false; end else begin i:=i + 1; j:=j + 1 end else
140
if i <= n - m + 1 then begin ifj>1
theni:=i-f[j-1] else i := i + 1;
j:=1; end end; if flag then writeln(f_out, 'Text hasn"t found');
Оцінимо тепер кількість порівнянь символів під час вико-нання цього алгоритму. Нескладно помітити, що під час кожного виконання тіла циклу значения t збільшується на 1 або значения j зменшується прииаймні на 1 присвоюванням /[/'] чи f[j - 1] + 1. Позначимо через b(t) початкове значения / під час чергового значения t = 1, 2, ..., n, а через w(t) — кількість змен-шень j під час відповідного значения t. Оскільки під час збіль-шення t значения у або не змінюється, або збільшується на 1, то маемо, що b(t) <= b(t - 1) - w(t) + 1 при t> 1.
Звідси w(t) <- b(t - 1) - b(t) + 1. Тоді:
w(l) + w(2) + ... + w(n) <= 1 + fe[l] - b[2] + 1 + 6[2] - b[3] + 1 + + ... + b[n - I] - b[n] + 1 - n + b[l] - b[n] <- n + 1.
3 алгоритму очевидно, що порівнянь символів відбувається рівио стільки, скільки збільшень t і зменшеньу' разом. Оскільки t збільшується п - 1 разів, загальна кількість порівнянь сим-волів не більша за 2п, тобто пропорційна п, і оцінюється як 0(л).
3 урахуванням побудови функції переходів загальна кіль-кість порівнянь символів для будь-яких рядків довжини п і т оцінюеться як О(т) + О(л), або 0(п + т).
Оскільки описаний метод є не дуже простим щодо його усві-домлення, то дозволимо собі ще раз зробити остаточний висновок у вигляді таких основных постулатів методу КМП-пошуку:
-
для пошуку наступного входження підрядка довжиною т у рядок довжиною п можна робити зсув лише на k позищй, де 1 <= k <- т;
-
якщо знайдено кількість позицій зсуву k для підрядка, то цеозначав, щох[1] = х[п -k +1], х[2] = х[т -k + 2],..., x[k] = х\т\, тобто існує такий початок підрядка в h символів, що збігається з його кінцем довжиною також у k символів. Це означав, що можна визначити наперед, на скільки позицій треба зсунути підрядок у межах від 1 до т, якщо попередне його повне накла-дання не збігається;
-
при накладанні з деякої позиції на рядок шуканого під-рядка незбігання можна визначити раніше, не переглядаючи
141
весь підрядок. Наприклад, незбігання відбулося на символі / заданого підрядка. Посилаючись на п. 1, можна зробити висно-вок, що тепер зсув можна робити лише в межах / символів порядка, де j < т. Посилаючись на п. 2, можна знайти таке ft., що визначає такий початок підрядка в ft. символів, що збігається з його кінцем.
/ Запитання для самоконтролю
1.Хто є авторами методу пошуку підрядка у рядку, який розгля-даеться?
-
Чому метод КМП-пошуку можна віднести до методу, що вико-ристовує скінченний автомат?
-
Поясніть поетапно ідею методу КМП-пошуку на конкретному прикладі, супроводжуючи свої пояснения мапюнками.
-
Що являє собою функція переходів для методу КМП-пошуку?
-
Який вміст елементів функції переходів для методу КМП-пошуку?
-
Наведіть рекурентний алгоритм побудови функції переходів для методу КМП-пошуку.
-
Запишіть наведений у пункті 6 алгоритм у вигляді фрагмента тексту Pascal-програми.
-
Наведіть рекурсивний алгоритм побудови функціі переходів для методу КМП-пошуку.
-
Запишіть наведений у пункті 8 алгоритм у вигляді тексту Pascal-процедури.
-
Запишіть алгоритм пошуку входження підрядка у рядок з вико-ристанням побудованоі функції переходів у вигляді фрагмента тексту Pascal-програми.
-
Сформулюйте основні постулати методу КМП-пошуку.
-
Якою є оцінка ефективності роботи методу КМП-пошуку? Обгрунтуйте цю оцінку.
Завдання
-
Реалізувати алгоритм пошуку підрядка х у рядку s, засто-сувавши метод КМП-пошуку. Для побудови функції переходів використати рекурентний алгоритм.
-
Реалізувати алгоритм пошуку підрядка х у рядку в, засто-сувавши метод КМП-пошуку. Для побудови функпд переходів використати рекурсивний алгоритм.
-
Протестувати алгоритм, реалізований у завданні 1, для конкретного прикладу.
-
Протестувати алгоритм, реалізований у завданні 2, для конкретного прикладу.
5- Порівняти результати виконання алгоритмів у завдан-нях 1, 2 стосовно кількості порівнянь символів підрядка і рядка з алгоритмом лінійного пошуку.
6. Зробити письмовий аналіз виконання завдання 5.
142