KMP字符串匹配算法(二)—KMP要点和证明

KMP字符串匹配算法(二)—KMP要点和证明

字符串的前缀函数 next[q]

定义函数 next(q)=max{kk<qPkPq} 为字符串 P 前缀函数
通俗的讲 next[q] 就是能作为 Pq 的真后缀的 Pq 的最长前缀 Pk 的长度 k

P的前缀函数迭代集 next[q]

next 迭代得到的集合记为 next[q] :

next[q]={next[q],next1[q]...nexti[q]...0}        (1)
其中 nexti[q]=next[nexti1[q]] ,括号中的迭代过程一直进行,直到某一步 nexti[q]=0 时停止迭代。

1. 用 P 的前缀函数对 q 迭代就能求出 Pq 的所有满足 PkPqk<q 的前缀 Pk 的长度 k (显然 Pk 既是 Pq 的真前缀又是 Pq 的真后缀)
证明: 参见算法导论第三版

next[q] 的扩展集合 Eq1

Eq1 中的元素为 {kP[k+1]=P[q]knext(q1)} , Eq1 中的元素 k 加1之后对应的前缀 Pk+1Pqk+1next[q] , 我们可以说 Eq1 是对应于 next[q] 的扩展集。( Eq1 中的每一个元素 i 加1之后的值 i+1 都会落到集合 next[q] 中, next[q] 的每一个元素 i 减1之后的值 i1 都会落到 Eq1 中)。

KMP字符串匹配算法(二)—KMP要点和证明_第1张图片

next[q] 的理论依据

好了,经过上面的铺陈,现在终于可以心安理得的求 next[q] 了:

next[q]={01+max{kEq1}Eq1=Eq1        (2)
根据这个结论,在求 next[q] 时,迭代地在 next[q1] 中按递减的顺序去寻找一个 k 满足 kEq1 ,这样,如果这样的 k 存在,那么第一个被找到的 k 就一定是 Eq1 集合中最大的 k ,根据等式(2), next[q]=k+1 ,如果不存在,好办了,根据等式(2)直接得到 next[q]=0
两点说明:
- 对 next[q1] 的迭代顺序按照等式(1)中从左到右的顺序: i=0,1,... ,只有这样才能保持迭代得到的待定项 k 是递减的。
- 0next[q1] ,所以迭代是良定义的,不用单独把0拿出来考虑。

代码

#include<iostream>
#include<string>

using namespace std;



void ComputeNext(string P,int *next,int const &n){//填上模式串P的next数组

    next[0] = 0;//对应于next[1]=0; 第一个字符的前缀函数值为0.

    int k(0);//k = next[0]=0;
    for (int i(1); i < n; i++){

        //【这里的i对应于第i+1个字符】---所以每一轮循环是在求【next[i+1]】
        while (k>0 && P[i] != P[k]){//初始的 k = next[i-1],由上一轮循环得到【对应于k=next[i]】
            //【这里P[i] != P[k]对应于P[i+1]!=P[k+1]】
                k = next[k];// 【对应于迭代k = next[next[i]]】
        }   

        if (P[i] == P[k])
            k = k + 1; 

        next[i] = k;//同时也是下一轮的k的初始值 k=next[i]

    }

}

/*对于模式串来说,我们会提前计算出每个匹配失败的位置应该移动的距离,花费的时间是常数时间*/
/*在已经匹配的模式串子串中,找出最长的相同的前缀和后缀,然后移动使它们重叠*/
int  KMP_v1(string T,string P){

    int shift(0);//T的与P匹配的第一个子串P'的第一个字符距离起始位置的偏移量。

    int lengthP = P.size();
    int lengthT = T.size();

    int *next = new int[lengthP];


    ComputeNext(P,next, lengthP);//计算前缀函数next


    for (int t(0) ,p(0),index(0); index <lengthT; index=t ){


        if (P[p] == T[t]){
            p++; t++;

            if (p == lengthP){ break; }//找到匹配,退出循环。
        }
        else{
            if (p>0){//P和T已经匹配了一部分字符。

                    p = next[p - 1];//进行下一次比较的P的字符的位置。
                }

            else t++;//P和T没有匹配上任何字符,也就是P和T这一轮第一次字符匹配就失败,这时就要向后移动t

            shift = t - p;//T的下一次比较中与P的首字符对齐的字符距离起始位置的位移量
        }
    }

    return shift;
}


int main(){

    string T;
    string P;

    cout << "输入文本T:" << endl;
    cin >> T;
    cout << "输入模式串P:" << endl;
    cin >> P;

    int s = KMP_v1(T,P);

    if (s < T.length()){
        cout <<P<<" 在 "<<T<<" 中偏移量 "<<s <<" 处"<< endl;
    }
    cout << endl;

    system("pause");
}

至于KMP算法该怎么利用前缀函数 next ,也就是说求得了 next 之后要怎么做,怎么利用 next 来加快匹配速度呢,我相信不做解释,看看代码,在草纸上画画就能明白了。在考虑 next 的作用时仔细想想它是怎么定义的。

你可能感兴趣的:(算法,KMP)