KMP算法浅析

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。

研究了两天依然懵懵懂懂 _(:зゝ∠)_,索性把自个理解的贴上来找点感觉。


传统暴力匹配

void NativeStrMatching( ElemType Target[], ElemType Pattern[] )            
{            
        register int TarLen = 0;        // Length of Target            
        register int PatLen = 0;        // Length of Pattern            

        // Compute the length of Pattern            
        while( '\0' != Pattern[PatLen] )            
                PatLen++;            

        while( '\0' != Target[TarLen] )            
        {            
                int TmpTarLen = TarLen;            
                for(int i=0; i<PatLen; i++)            
                {            
                        if( Target[TmpTarLen++] != Pattern[i] )            
                                break;            
                        if( i == PatLen-1 )            
                                cout<<"Native String Matching,pattern occurs with shift "<<TarLen<<endl;            
                }            
                TarLen++;            
        }            
}   

传统的匹配方法通过从Target[0]开始循环不停的遍历Pattern妄图与Target进行匹配。如若失配则从Target[1]继续重复上一步骤,直到整个Pattern在Target中找到匹配,或者已经扫描完整个目标串也没能够完成匹配为止。

这样的操作简单,容易实现,但是大多数情况下会出现很多多余的判断。

比如假设Target=“abcabdaabcdef”
Pattern=“abcd”

如传统匹配方法,我们要逐一进行一下步骤
这里写图片描述

然而在第一次匹配中,target和pattern的前三个字符匹配成功,而第
四个字符失配。继而指针移动到target[1]继续进行匹配。也就是第二步。

这里写图片描述

咱们肉眼可以看出,在pattern中,pattern[0] != pattern[2] != pattern[3],并且target[0] = pattern[0] …..target[3] = pattern[3]。
所以pattern[1]和[2] 绝对不可能匹配到target[1]和[2],所以2、3步骤其实是多余了,而KMP算法就是用代码实现我们肉眼所能判断到的多余的步骤并跳过它。


KMP算法

在失配后,并不简单地从目标串下一个字符开始新一轮的检测,而是依据在检测之前得到的有用信息,直接跳过不必要的检测,从而达到一个较高的检测效率。
当第一次失配后,并不直接开始实施第二步,而是通过一些信息,直接跳过后几个肯定不可能匹配的冗余字符,而直接让模式串Pattern从目标串的target[3]开始新一轮的检测,从而达到了减少循环次数的效果。

覆盖函数

void CptPfFunc(char Pattern[], int PrefixFunc[])
{
    register int iLen = 0;    // Length of Pattern[]            
    while ('\0' != Pattern[iLen])
        iLen++;

    int LOLP = 0;     // Lenth of longest prefix
    PrefixFunc[1] = 0;

    for (int NOCM = 2; NOCM<iLen + 1; NOCM++)     // NOCM represent the number of characters matched            
    {
        while (LOLP>0 && (Pattern[LOLP] != Pattern[NOCM - 1]))
            LOLP = PrefixFunc[LOLP];
        if (Pattern[LOLP] == Pattern[NOCM - 1])
            LOLP++;
        PrefixFunc[NOCM] = LOLP;
    }
}

覆盖函数所表征的是pattern本身的性质,可以让为其表征的是pattern从左开始的所有连续子串的自我覆盖程度。
理解KMP的核心就在于理解覆盖函数。简单地说覆盖函数的作用就是标示头尾相同字符串的索引位置(从0开始)。如下:

func(a)          = -1  #没有对称字符
func(ab)         = -1
func(aba)        = 0
func(abaa)       = 0
func(abaab)      = 1   #ab首尾相同
func(abaabcaba)  = 2   #aba首尾相同

上个实例:
比如字符串: “abccabccabca”

NOCM 表示 已经匹配的字符数
LOLP 表示 既是自身真后缀又是自身最长前缀的字符串长度
以下是计算流程:

LOLPNOCMLOLPPrefixFunc
020PrefixFunc[2] = 0
030PrefixFunc[3] = 0
020PrefixFunc[4] = 0
040PrefixFunc[5] = 0
051PrefixFunc[5] = 1
162PrefixFunc[6] = 2
273PrefixFunc[7] = 3
384PrefixFunc[8] = 4
495PrefixFunc[9] = 5
5106PrefixFunc[10] = 6
6117PrefixFunc[11] = 7
712

最后我们可以得出PrefixFunc[] = { 0,0,0,0,1,2,3,4,5,6,7,1 }


KMP

void KMPstrMatching( ElemType Target[], ElemType Pattern[] )            
{            
        int PrefixFunc[MAX_SIZE];            
        register int TarLen = 0;            
        register int PatLen = 0;            

        // Compute the length of array Target and Pattern            
        while( '\0' != Target[TarLen] )            
                TarLen++;            

        while( '\0' != Pattern[PatLen] )            
                PatLen++;            

        // Compute the prefix function of Pattern            
        CptPfFunc( Pattern, PrefixFunc );            

        int NOCM = 0;     // Number of characters matched            

        for( int i=0; i<TarLen; i++ )            
        {            
                while( NOCM>0 && Pattern[NOCM] != Target[i] )            
                        NOCM = PrefixFunc[NOCM];            
                if( Pattern[NOCM] == Target[i] )            
                        NOCM++;            
                if( NOCM == PatLen )            
                {            
                        cout<<"KMP String Matching,pattern occurs with shift "<<i - PatLen + 1<<endl;            
                        NOCM = PrefixFunc[NOCM];            
                }            
        }            
}

解决了覆盖函数KMP算法就没什么了,如上代码所示。
当字符串失配时,咱的Target就不动了,只需要通过覆盖函数求出的PrefixFunc移动pattern就好。
而当pattern[0] != target[x] 时,就移动target咯。

懒得画了盗个图:
ps: 图中有一个错误,index=8时不应该跳过

这里写图片描述


补一个自己写的案例吧
#include <iostream>
using namespace std;
#define MAXSIZE 64

void getNextVal(const char T[], int next[])
{
    int i = 1;
    int j = 0;
    next[1] = 0;

    while (T[i])
    {
        // 如果是开头或者匹配成功的话双方向前移动
        if (j == 0 || (T[i] == T[j])) 
        {
            ++i;
            ++j;
            // 
            if (T[i] != T[j])
            {
                // 
                next[i] = j;
            }
            else
            {
                // 如果还是相等
                next[i] = next[j];
            }
        }
        // 如果不匹配,则寻找T[next[j]]与T[i]进行匹配
        else
        {
            j = next[j];
        }
    }
}


void Kmp(const char *target, const char *pattern)
{
    int next[MAXSIZE];
    int tarlen = 0;
    int patlen = 0;

    while ('\0' != target[tarlen])
    {
        tarlen++;
    }
    while ('\0' != pattern[patlen])
    {
        patlen++;
    }

    getNextVal(pattern, next);

    int j = 0;

    for (int i = 0; i < tarlen; ++i)
    {
        while (j > 0 && pattern[j] != target[i])
            j = next[j];
        if (pattern[j] == target[i])
            j++;
        if (j == patlen)
        {
            cout << "KMP String Matching,pattern occurs with shift " << i - patlen + 1 << endl;
            j = next[j];
        }
    }
}

int main()
{
    char* target = "annbcdanacadannabnncsannswawqazcxxxannabnnaaa";
    char* pattern = "annabnna";
    Kmp(target, pattern);

    system("pause");
    return 0;
}

参考博客:
http://blog.csdn.net/power721/article/details/6132380 (图片来源)

参考视频:
上:http://v.youku.com/v_show/id_XODYxNjExODQ=.html 第 34分钟开始
下:http://www.56.com/u28/v_NjAwMzA0ODA.html
严蔚敏 // 其实大多数是从视频上搞清楚的,推荐大家看一下视频

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值