字符串匹配算法

字符串匹配(string match)是在实际工程中经常会碰到的问题,通常其输入是原字符串(String)和子串(又称模式,Pattern)组成,输出为子串在原字符串中的首次出现的位置。通常精确的字符串搜索算法包括暴力搜索(Brute force),KMP, BM(Boyer Moore), sunday, robin-karp 以及 bitap。下面分析这几种方法并给出其实现。假设原字符串长度M,字串长度为N。

1. Brute force.

该方法又称暴力搜索,也是最容易想到的方法。也叫朴素字符串匹配算法,NAIVE-STRING-MATCHER

预处理时间 O(0)

匹配时间复杂度O(N*M)

主要过程:从原字符串开始搜索,若出现不能匹配,则从原搜索位置+1继续。

//无视我的库文件
#include<iostream>
#include<string>
#include <math.h>
#include <algorithm>
#include <vector>
#include <functional>
using namespace std;
int bf(string text, string find)
{
    auto m = text.size(), n = find.size();
    if (0 == m*n || m < n) return -1;
    string::iterator p, q, s;
    s = text.begin();
    p = text.begin();
    q = find.begin();
    while (p != text.end() && (text.end() - s) >= n)
    {
        if (*p == *q)
        {
            p++;
            q++;
        }
        else
        {
            s++;
            p = s;
            q = find.begin();
        }
        if (q == find.end()) return s - text.begin();
    }
    return -1;
}
int main()
{
    const string text{ "introduction to algorithms" };
    const string find{ "algorithm" };
    cout << bf(text, find) << endl;
    system("pause");
}

假设模式P中的所有字符都是不同的。试说明如何对一段n个字符的文本T加速朴素字符串匹配的执行速度,使其运行时间达O(n).——算法导论32.1-2

主要过程:这里所有字符不同说明对T遍历的时候不需要回过来,只需要把模式P中的下标记为起始就可以了。

int bf_unique(string text, string find)
{
    auto m = text.size(), n = find.size();
    if (0 == m*n || m < n) return -1;
    for (auto p = text.begin(), q = find.begin(); p != text.end(); ++p)
    {
        if (*p == *q&&q != find.end()) ++q;
        else
        {
            if (q!=find.begin())
            {
                q = find.begin();
                --p;
                continue;
            }   
        }
        if (find.end() == q) return p - text.begin() - find.size() + 1;//从0开始
    }
    return -1;
}

RabinKarp算法

算法思想是将字符串映射到一个数字,然后比较字串的数字是否相等,如果相等则继续进一步判断确认正确性,如果不相等,则可以判断字符串不匹配。

预处理时间O(0)

最坏匹配时间复杂度O(N*M)

int RabinKarpMatcher(string st, string sp, int d, int q)
{
    int n = st.length(), m = sp.length(), h = ((int)pow(d, m - 1)) % q;
    int p = 0, t = 0;
    //计算p和t0
    for (int i = 0; i < m; ++i)
    {
        p = (p*d + sp[i]) % q;
        t = (t*d + st[i]) % q;
    }

    for (int s = 0; s < n - m + 1; ++s)
    {
        if (p == t&&sp == st.substr(s, m))
        {
            return s;//如果找多个应该先存起来,或者这边输出但是别return
        }
        t = (d*(t - st[s] * h) + st[s + m]);
        t = ((t < 0) ? q : 0) + t%q;//如果上式直接取余数的话会遇到负号的问题,这里判断下。
    }
    return -1;
}

KMP

KMP是经典的字符串匹配算法。

预处理时间:O(M)

匹配时间复杂度:O(N)

主要过程:通过对字串进行预处理,当发现不能匹配时,可以不进行回溯。

void computePrefixFunction(string sp, int* a)
{
    int m = sp.length(), k = 0;
    a[0] = 0;
    for (int q = 1; q < m; ++q)
    {
        while (k>0 && sp[k] != sp[q])//加到一定程度和不匹对的情况;
            k = a[k-1];//这边也是从0开始的,所以还是要减1。。
        if (sp[k] == sp[q])
            ++k;
        a[q] = k;
    }
//  return;
}
void kmpMatcher(string st, string sp)
{
    int n = st.length(), m = sp.length(), q = 0;
    int* p = new int[m];
    computePrefixFunction(sp, p);
    for (int i = 0; i < n; ++i)
    {
        while (q>0 && sp[q] != st[i])
            q = p[q - 1];
        if (sp[q] == st[i])
            q++;
        if (q == m)//因为上面提前+1了;
        {
            cout << i - m + 1 << endl;//从0开始计算。
            q = p[q - 1];//注意数组不要越界。
        }
            
    }
    delete []p;
//  return -1;//如果以返回值的方式那就只能输出第一个了。要输出多个要么提前保存,要么直接输出。
}

未完待续

续一秒更健康

点赞