算法复习——KMP算法(字符串匹配)

字符串匹配问题描述:

定义:如果给定字符串T以及字符串S,|T|>=|S|,存在正整数i,使得T[i, i+|S|] = S,则称S为T的子串。

给定一个模式字符串pat,以及一个字符串str,问str是否为pat的字串,如果是,给出子串在pat中开始的位置。

 

一个很直观的暴力解决方法是:

定义两个指针i,j。

0)初始时,i指向pat[0],j指向str[0]。

1)检查str是否为pat以i为起始位置的字串,如果是,则返回i。

2)i = i+1,j = 0。如果i > i -len(str),则返回-1,表示没有找到字串,否则回到1)。

 

这个方法虽然直观,但是速度会很慢。原因是没有利用已经检索过的指针j之前的字符串信息。D.E.Knuth,J.H.Morris和V.R.Pratt同时(这也能同时?)发明了KMP算法用来解决字符串匹配问题,人们用他们三个人的名字的首字母命名了这个算法。

 

KMP算法思路:

1)构建一个next数组。数组值为整数,长度与str相同。next的含义是,字符串str[0,..j-1]中,前缀等于后缀的最长长度。

                                                  next[j] = \max_{k}(str[0,k-1] = str[j-k, j-1])

2)每当匹配失败pat[i] != str[j]的时候,令j = next[j],重新匹配。

 

这里涉及到的关键就是next数组的构建(getnext函数)。下面进行简单说明:

1)规定next[0] = -1,方便进行边界控制

2)int k,j,k和j的含义是,当前next[j]=k。下一步要计算next[j+1]

3)当str[j] == str[k]的时候。由k,j的含义可以知道,str[0,..,k-1] = str[j-k,j-1],则str[0,k] = str[j-k,j],也就是说,next[j+1] = k+1。即next[j+1] = next[j] + 1。

4)当str[j] != str[k]的时候。我们的目标是要找到str[0, j]的对应的前缀等于后缀的最长长度。

已经知道,str[0, k-1] = str[j-k, j-1],str[k] != str[j],也就是说,最长前缀等于后缀,对于str[0,k]和str[j-k,j]已经没有可能了。这样,我们需要调整k,减少这个最长前缀。下一个可能,就是str[0, next[k]]。(这里可以手工画图,好好推导)

 

vector getnext(const string& str){
    vector next(str.size(), 0);
    next[0] = -1;
    int k = -1, j = 0;
    while(j < str.size()){
        if(k == -1 || str[j] == str[k]) {
            next[++j] = ++k;
        } else {
            k = next[k];
        }
    }
    return next;
}

int kmp(const string& pat, const string& str){
    vector next = getnext(str);
    int i = 0, j = 0, l = str.size();
    while(j < l){
        if(j == -1 || pat[i] == str[j]) {
            i++;
            j++;
        } else {
            j = next[j];
        }
    }
    return (j == l) ? i - l : -1;
}

 

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