字符串查找算法 KMP

当我们在查找字符串 str的子串subStr时,大致的想法是设定两个指针i j,分别指向str subStr的头,逐个比较,当字符相同时,++j,当不符合时++i j = 0。直到匹配到strsubStr尾,比较j是否等于subStr.length(),相等则表明匹配成功,且起始索引为i;否则不存在,返回-1。对于特殊的情况,subStr为空的情况下返回 0。

这种算法复杂度为n * m其中 m,n 为 str subStr 的长度。

但其实如果事先对subStr进行分析,就可以避免一些不必要的比较。如果subStr = aaaastr = aaabaaaa,当匹配到 i = 0, j = 3时,a != b,这时候 i + 1,j = 0,然后就可以直接比较 subStr[2] 和 str[3],因为当我们匹配到 b 时,aaaa 和 aaab 已经知道匹配过了 3 个 a,后退一位后,也还有两个 a 已经匹配过,所以可以跳过直接匹配第三位。

所以,可以创建一个 next 数组代表成功匹配到 j 位置时,如果后一个字符匹配失败可以跳过多少个字符。这就是 KMP 算法的原理。

例如 aaaa,第一个字符不算,则可以得到 0123(最后一个 3 没什么用)。对于上一个例子的 aaabaaaa 中,j = 3 时 ’b‘ 处匹配失败,则根据 next 数组,可以取 next[2] = 2,让 b 和 sub[2] 进行比较,如果还不匹配,则继续查询 next 数组直到可以跳为 0。

#include <iostream>
#include <string.h>

using namespace std;

int* GetJumpMap(string str)
{
    int* a = new int[str.length()];
    int count = 0;
    a[0] = 0;
    for (int i = 1; i < str.length(); ++i)
    {
        if (str[i] == str[count])
        {
            ++count;
            a[i] = count;
        }
        else
        {
            while(count > 0 && str[i] != str[count])
            {
                count = a[count - 1];
            }

            if (str[i] == str[count])
            {
                ++count;
            }

            a[i] = count;
        }
    }

    return a;
}

int IndexOf(string str, string subStr)
{
    if (subStr.length() == 0)
    {
        return 0;
    }

    if (str.length() == 0)
    {
        return -1;
    }

    int* next = GetJumpMap(subStr);

    int i = 0;
    int j = 0;
    while (str[i] != '\0' && subStr[j] != '\0')
    {
        if (str[i + j] == subStr[j])
        {
            ++j;
        }
        else if (j > 0 && next[j - 1] != 0)
        {
            i = i + j - next[j - 1];
            j = next[j - 1];
        }
        else
        {
            ++i;
            j = 0;
        }
    } 

    if (j == subStr.length())
    {
        return i;
    }
    else
    {
        return -1;
    }
}

Comments