Классические алгоритмы поиска по шаблону

Задача: найти все вхождения строки-шаблона p (длина m) в текст t (длина n).

Сравнивает шаблон с каждой позицией текста. O(n·m).

func bruteForce(text, pattern string) []int {
    var result []int
    n, m := len(text), len(pattern)
    for i := 0; i <= n-m; i++ {
        if text[i:i+m] == pattern {
            result = append(result, i)
        }
    }
    return result
}

Вычисляет хеш шаблона, затем скользит по тексту, сравнивая хеши. При совпадении — дополнительная проверка строк. Среднее O(n+m), худшее O(n·m).

Ключевой приём — rolling hash: хеш следующего окна вычисляется за O(1) из предыдущего.

const base, mod = 31, 1_000_000_007

func rabinKarp(text, pattern string) []int {
    n, m := len(text), len(pattern)
    ph := hash(pattern)
    wh := hash(text[:m])
    pow := 1
    for i := 0; i < m-1; i++ { pow = pow * base % mod }

    var result []int
    if wh == ph && text[:m] == pattern { result = append(result, 0) }
    for i := 1; i <= n-m; i++ {
        wh = (wh - int(text[i-1]-'a'+1)*pow%mod + mod) % mod
        wh = (wh*base + int(text[i+m-1]-'a'+1)) % mod
        if wh == ph && text[i:i+m] == pattern {
            result = append(result, i)
        }
    }
    return result
}

func hash(s string) int {
    h := 0
    for _, c := range s { h = (h*base + int(c-'a'+1)) % mod }
    return h
}

Предвычисляет таблицу «провалов» (failure function) — для каждой позиции шаблона показывает, куда откатиться при несовпадении. O(n+m), без дополнительных сравнений.

func kmpSearch(text, pattern string) []int {
    lps := buildLPS(pattern)
    var result []int
    j := 0
    for i := 0; i < len(text); {
        if text[i] == pattern[j] { i++; j++ }
        if j == len(pattern) {
            result = append(result, i-j)
            j = lps[j-1]
        } else if i < len(text) && text[i] != pattern[j] {
            if j != 0 { j = lps[j-1] } else { i++ }
        }
    }
    return result
}

func buildLPS(p string) []int {
    lps := make([]int, len(p))
    length, i := 0, 1
    for i < len(p) {
        if p[i] == p[length] { length++; lps[i] = length; i++ } else if length != 0 { length = lps[length-1] } else { i++ }
    }
    return lps
}

Сравнивает шаблон справа налево, использует две эвристики для больших пропусков. На практике быстрее KMP для длинных шаблонов и больших алфавитов. O(n/m) в лучшем случае.

Две таблицы:

  • Bad character — при несовпадении сдвиг по последнему вхождению символа в шаблоне.
  • Good suffix — сдвиг по уже совпавшему суффиксу.

Строит автомат для одновременного поиска множества шаблонов. O(n + Σ|pi| + k), где k — число совпадений. Используется в антивирусах, системах фильтрации.

Строится как trie из всех шаблонов с добавлением fail-ссылок (аналог LPS у KMP).

АлгоритмВремяПредобработкаКогда использовать
Brute ForceO(n·m)Короткие строки, простота
Rabin-KarpO(n+m) avgO(m)Несколько шаблонов, rolling hash
KMPO(n+m)O(m)Один шаблон, гарантия
Boyer-MooreO(n/m) bestO(m+σ)Длинные шаблоны, большой алфавит
Aho-CorasickO(n+Σm+k)O(Σm)Множество шаблонов одновременно

ЗаданиеСложность
1Упражнение 1easy
2Упражнение 2easy
3Упражнение 3medium
4Упражнение 4medium