KMP和朴素匹配的字符串匹配算法

KMP和朴素匹配解决的问题:

  • 字符串匹配。给你两个字符串,寻找其中一个字符串是否包含另一个字符串,如果包含,返回包含的起始位置。
    如下面两个字符串:
    char *str = “abcababcabc”;
    char *ptr = “abcbac”;
    str有一处包含ptr
    在str的下标5处开始包含ptr。

问题很简单,下面来分别介绍两种算法:
朴素匹配算法说明:

  • 朴素匹配又称暴力匹配,方法是每次匹配一个字符。简单来说就是str的每一个字符作为子串的开头,与要匹配字符串ptr进行匹配。对主串str做大循环,每个字符开头做strlen(ptr)长度的小循环,直到匹配成功或全部遍历完成为止。如图:
  • KMP和朴素匹配的字符串匹配算法_第1张图片
    朴素匹配代码实现:
int BF(const char *str, const char *ptr)
{
    int lenstr = strlen(str);
    int lenptr = strlen(ptr);
    int i = 0;
    int j = 0;
    while(i < lenstr && j < lenptr)
    {
        if(str[i] == ptr[j])
        {
            i++;
            j++;
        }
        else
        {
            i = i - j + 1;
            j = 0;
        }
    }
    if(j == lenptr)
    {
        return i-j;
    }
    return -1;
}

KMP算法说明:

  • 由朴素匹配可知,我们从目标字符串str(假设长度为n)的第一个下标选取和ptr长度(长度为m)一样的子字符串进行比较,如果一样,就返回开始处的下标值,不一样,选取str下一个下标,同样选取长度为n的字符串进行比较,直到str的末尾(实际比较时,下标移动到n-m)。这样的时间复杂度是O(n*m)

  • KMP算法:可以实现复杂度为O(m+n)

  • 怎样提升效率?
    充分利用了目标字符串ptr的性质(比如里面部分字符串的重复性,即使不存在重复字段,在比较时,实现最大的移动量),str的下标不回退,回退ptr的下标到最适位置来减少匹配的次数。如图:
    KMP和朴素匹配的字符串匹配算法_第2张图片

  • 这里附上《大话数据结构》一书中对KMP算法的推演:KMP和朴素匹配的字符串匹配算法_第3张图片

  • 如何确定ptr回退的最适位置?
    这里我们要实现一个函数getnext()来得到一个next数组,该数组的值就为ptr回退的最适位置。
    next数组的含义就是一个固定字符串的最长前缀和最长后缀相同的长度。求解的规则为:在目标字符串中存在两个真子串一个以0位置开始,另一个以j-1位置结束,两真子串的长度就为next[j]的值。

    比如:abcjkdabc,那么这个数组的最长前缀和最长后缀相同必然是abc。
    cbcbc,最长前缀和最长后缀相同是cbc。
    abcbc,最长前缀和最长后缀相同是不存在的。
    **注意最长前缀:是说以第一个字符开始,但是不包含最后一个字符。
    比如aaaa相同的最长前缀和最长后缀是aaa。**

对于目标字符串ptr,“abcabc”,长度是6,所以需求出next[0],next[1],next[2],next[3],next[4],next[5]的值:

我们通过一个复杂一点的例子来解释next数组的求解方法:
首先我们规定next[0] = -1 、next[2] = 0,我们通过一个图来体现next数组的求解:
KMP和朴素匹配的字符串匹配算法_第4张图片

  • 由图中的例子我们求得ptr的next数组为next[]={-1, 0, 0, 1, 2};

代码实现:

void Getnext(const char *ptr, int *next, int len)
{
    next[0] = -1;
    next[1] = 0;
    int i = 2;
    int k = 0;
    while(i < len)
    {
        if(k == -1 || ptr[i - 1] == ptr[k])
        {
            next[i++] = ++k;
        }
        else
        {
            k = next[k];
        }
    }
}

next数组的求解过程用语言很难表达出来(我基本都是靠自己理解过来的),所以的下去多做几次求解过程加深其求解原理。

KMP算法的实现:

  • 这个和next很像,原理不好表达,具体就看代码(其实上面已经大概说完了整个匹配过程)。
int KMP(cont char *str, const char *ptr, int pose)//pose表示开始匹配位置
{
    int len_str = strlen(str);
    int len_ptr = strlen(ptr);
    int i = pose;
    int j = 0;
    int *next = (int *)malloc(sizeof(int) * len_ptr);
    Getnext(ptr, next, len_ptr);

    while(i < len_str && j < len_ptr)
    {
        if(str[i] == ptr[j] || j == -1)
        {
            i++;
            j++;
        }
        else
        {
            j = next[j];
        }
    }
    if(j != len_ptr)
    {
        return -1;
    }
    return i - j;
}

你可能感兴趣的:(数据结构与算法)