Який алгоритм пошуку рядків насправді найшвидший?


27

Я деякий час застряг, на якому найшвидший алгоритм пошуку рядків, почув багато думок, але врешті-решт я не впевнений.

Я чув, як деякі люди кажуть, що найшвидший алгоритм - Бойєр-Мур, а деякі говорять, що Кнут-Морріс-Пратт насправді швидший.

Я шукав складність обох, але вони в основному виглядають однаково O(n+m). Я виявив, що в гіршому випадку Бойєр-Мур має O(nm)складність порівняно з Кнут-Морріс-Пратт, який має O (m + 2 * n). Де n = довжина тексту і m = довжина візерунка.

Наскільки я знаю, у Бойєра-Мура є лінійний найгірший час, якщо я використовую правило Галіля.

Моє запитання: Понад усе, що насправді є найшвидшим алгоритмом пошуку рядків (Це питання включає всі можливі алгоритми жала не лише Боєра-Мура та Кнута-Морріса-Пратта).

Редагувати: Завдяки цій відповіді

Я саме шукаю:

З огляду на текст Tі візерунок, Pя повинен знайти всі види Pв T.

Також довжина P і T [1,2 000 000]починає з, і програма повинна запускатись за 0,15 сек.

Я знаю, що KMP і Rabin-Karp достатньо, щоб отримати 100% оцінку на проблему, але я, наприклад, хотів спробувати застосувати Boyer-Moore. Що було б найкраще для цього типу пошуку шаблонів?


6
Коли ви тестували їх на своїй обраній мові, що ви знайшли?
Вальтер

4
На деяких тестах Бойєр-Мур був кращим, на інших KMP був кращим, але я не впевнений, що я маю "найкращу" реалізацію їх. Щодо мови вибору, вона знаходиться в тегах: C ++ (не впевнений, чи бачив ви це, оскільки ви написали "мову вибору"). PS Я також не впевнений, чи перевірений я на кращих тестах.
vandamon taigi


Кнут-Морріс-Пратт, який має O (m + 2 * n) ... Ви маєте на увазі O (m + n).
Жуль

Виберіть один із пристойною алгоритмічною складністю, а потім мікронастроюйте лайно з ним із профілером у руці - для мене завжди працювало. :-D

Відповіді:


38

Це залежить від типу пошуку, який ви хочете виконати. Кожен з алгоритмів працює особливо добре для певних типів пошуку, але ви не вказали контекст ваших пошукових запитів.

Ось кілька типових думок про типи пошуку:

  • Boyer-Moore: працює за попереднім аналізом схеми та порівнянням справа наліво. Якщо трапляється невідповідність, початковий аналіз використовується для визначення того, наскільки шаблоном можна змістити wrt текст, який шукається. Це особливо добре працює для довгих моделей пошуку. Зокрема, він може бути сублінійним, оскільки вам не потрібно читати кожен окремий символ вашого тексту.

  • Кнут-Морріс-Пратт: також попередньо аналізує схему, але намагається повторно використовувати все, що вже було узгоджено в початковій частині шаблону, щоб уникнути необхідності його повторного узгодження. Це може спрацювати досить добре, якщо ваш алфавіт невеликий (f.ex. бази ДНК), оскільки ви маєте більший шанс, що ваші шаблони пошуку містять повторно використані підпробочки.

  • Aho-Corasick: Потрібно багато попередньої обробки, але це робиться для ряду моделей. Якщо ви знаєте, що будете шукати одні й ті ж моделі пошуку знову і знову, то це набагато краще, ніж інші, тому що вам потрібно аналізувати шаблони лише один раз, а не один раз за кожний пошук.

Отже, як зазвичай в CS, немає однозначної відповіді на загальний кращий . Це швидше питання вибору правильного інструменту для роботи.

Ще одне зауваження щодо ваших найгірших міркувань: Подумайте про види пошуку, необхідні для створення найгіршого випадку, і добре подумайте, чи справді вони є актуальними у вашому випадку. Наприклад, в O(mn)гіршому випадку складність алгоритму Бойєр-Мура випливає з шаблону пошуку і тексту , що кожне використання тільки один символ (наприклад , знаходження aaaв aaaaaaaaaaaaaaaaaaaaa) - чи дійсно ви повинні бути швидкими для пошуку , як це?


У мене є цілий англійський алфавіт або близько нього, і я оновив питання, вибачте за те, що не почав це з просити.
vandamon taigi

І так, мені потрібно бути швидким навіть для таких пошуків
vandamon taigi

1

Хоча я трохи запізнююся відповісти на це питання, але я думаю, що Z-Algorithmце набагато швидше, ніж будь-які його колеги. Найгірша його складність - O (m + n), і вона не вимагає попередньої обробки шаблону / тексту. Це також дуже просто кодувати порівняно з іншими алгоритмами.

Це працює наступним чином.

Наприклад, є рядок S ='abaaba'. Ми повинні знайти z(i)значення для i=0 to len(S)-1. Перш ніж заглиблюватися в пояснення, дозвольте спершу викласти деякі визначення.

z(i)= ні. символів префікса, Sщо відповідає префіксу s(i).

s(i)= ithсуфікс S.

Нижче наведені s(i)значення для s = 'abaaba'.

s(0) = 'abaaba' = S
s(1) = 'baaba'
s(2) = 'aaba'
s(3) = 'aba'
s(4) = 'ba'
s(5) = 'a'

Значення z відповідно

z(0) = 6 = length(S)
z(1) = 0
z(2) = 1
z(3) = 3
z(4) = 0
z(5) = 1

Для детального розуміння алгоритму див. Наступні посилання.

http://codeforces.com/blog/entry/3107

https://www.youtube.com/watch?v=MFK0WYeVEag

Тепер потрібно O (N), щоб знайти всі zзначення без попередньої обробки. Мені було б цікаво зараз, як ви можете використовувати цю логіку для відповідності шаблону в заданому рядку?

Подивимось з прикладом. Шаблон (Р): aba, Текст (Т): aacbabcabaad.

Покладіть це у форму P $ T. ( $- будь-який символ, який не відображається ні в шаблоні, ні в тексті. Я $трохи пізніше прийду до важливості .)

P$T = aba$aacbabcabaad

Ми знаємо len(P)= 3.

Усі z значень z P$Tє

z(0) = 16 = len(P$T)
z(1) = 0
z(2) = 1
z(3) = 0
z(4) = 1
z(5) = 1
z(6) = 0
z(7) = 0
z(8) = 2
z(9) = 0
z(10) = 0
z(11) = 3
z(12) = 0
z(13) = 1
Z(14) = 1
Z(15) = 0

Тепер який z(i)= len(P). Ans = 11.Тож наш шаблон присутній на Ans-len(P)-1= 7. -1є для $характеру.

Тепер, чому $чи будь-який такий особливий персонаж, важливо. Розглянемо P = 'aaa'і T = 'aaaaaaa'. Без особливого характеру всі z(i)матимуть додаткові значення. Ще можна знайти положення шаблону в тексті за допомогою наведених нижче формул:

Умова: z(i)> = len(P)і положення: Ans-len(P). Але умова в цьому випадку стає трохи хитрою і заплутаною. Я особисто вважаю за краще використовувати техніку особливих символів.


1
Чи можете ви це пояснити самі? Наявність посилань на зовнішні сайти можна використовувати для розробки, але суть відповіді повинна полягати в самій відповіді, а не в тому, щоб переходити посилання на інший сайт.

Z-алгоритм в основному такий же, як kmp. Сумніваюсь, це набагато швидше.
Томас Ейл

2
Я згоден з @ThomasAhle. Обчислення z - це попередня обробка. Хоча це хороше пояснення. Завдяки цій відповіді я створив O(n)спосіб перетворення з KMP попередньої обробки на попередню обробку Z. Тут
leewz

-1

Використовуйте адресну пам'ять для вмісту , реалізовану в програмному забезпеченні у вигляді віртуальної адресації (вказівка ​​літер на літери).

Це щось зайве для середнього алгоритму відповідності рядків.

CAM може порівнювати величезну кількість шаблонів одночасно, приблизно до 128-літерних шаблонів (якщо вони ASCII; якщо вони є тільки Unicode 64). І це один дзвінок на довжину літери в рядку, до якого потрібно відповідати, і один випадковий зчитування з пам'яті на довжину максимальної довжини шаблону. Отже, якщо ви аналізували рядок букв 100 000, з одночасно до 90 000 000 шаблонів (що знадобиться приблизно 128 ГБ, щоб зберігати велику кількість шаблонів), знадобиться 12 800 000 випадкових зчитувань з оперативної пам'яті, так що це станеться за 1 мс.

Ось як працює віртуальна адресація.

Якщо я розпочну з 256 стартових адрес, які представляють першу букву, ці літери вказують на 256 наступних літер. Якщо шаблон не існує, ви не зберігаєте його.

Тож якщо я продовжую пов'язувати листи з літерами, це як би мати 128 фрагментів віртуальної адреси, що вказують на віртуальну адресацію.

Це спрацює - але, щоб одночасно збігатися до 900 000 000 шаблонів, є ще один останній трюк, який слід додати до нього, - і це скористатися тим, що ви починаєте з великого повторного використання цих буферів букв, але згодом вони розсіюються. Якщо ви перерахуєте вміст, замість того, щоб виділити всі 256 символів, він сповільнюється дуже мало, і ви отримаєте 100-кратне збільшення ємності, тому що ви, зрештою, отримуєте лише 1 букву, що використовується у кожному буфері вказівника букв (який я охрестив ' Втеча').

Якщо ви хочете отримати відповідність рядків найближчого сусіда, у вас є багато таких запущених паралельно, і ви збираєтеся в ієрархії, тож ви поширюєте свою помилку неупереджено. якщо ви спробуєте найближчого сусіда лише з одним, то ви упереджено ставитесь до початку дерева.


4
@MagnusRobertCarlWoot, враховуючи, що у вас такий же гаватар, що і roucer81, це астрономічний збіг зіткнення хеш-коду або у вас однакова адреса електронної пошти. Якщо ви однакова особа за обома обліковими записами, вам слід скористатися формою «зв’язатися з нами», щоб об’єднати їх, щоб ви отримали належну належність за репутацію, здобуту в результаті відповідей на цю відповідь.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.