KMP算法的学习(转)

KMP算法的学习

今天做题目遇到了一个关于字符串匹配的问题,似乎用传说中的KMP算法比较高效率,因为测试数据估计很BT,一般的字符串匹配算法需要不断的指针回退,效率比较低。

KMP算法所要解决的问题描述如下:
有文本串S, 比如 acabaabaabcacaabc
有搜索串P, 比如 abaabcac
要从S中找到P。

一般的解法如下:

i=0;
j=0;
len_s = strlen(S);
len_p = strlen(P);
while (i <= len_s && j <= len_p) {
    if ( S[i] == S[j] ) {
        ++i;
        ++j;
    } else {
        i = i-j+2;
        j = 0;
    }   
}

这个算法容易理解,S(i-j+1)S(i-j+2)...S(i) == P0P1P2..Pj,当S(i+1) != P(j+1)时,说明匹配失败,那么j回退到0, i回退到i-j+2(注意不是i-j+1,如果那样的话就死循环了), 然后继续Si跟Pj的比较。

 

这里面效率低的原因是,j回退的幅度太大,一下子变成0,当然, 一般情况下P是比较短的,那么回退到0影响并不是特别大,但是i回退的幅度也是很大的,一下子中间的比较成果变成0了,
KMP算法就是让i不回退,j回退一部分(最坏情况下回退全部)的算法。

如何做到这一点呢,举个例子:
S=gggabcabc
P=abcac
S3S4S5S6 = P1P2P3P4 = abca, 但是S7 != P5, 一般的算法的话,S的指针i回退到4, P的指针j回退到0。KMP算法的话, 会发现 abca =a bc a,开始跟末尾的子字符串是一样的,都是"a",也就是说,如果i不回退,让j回退到1,下次重新比较的话从S7 <=> P1 开始,因为刚才匹配失败时,S7之前的字符是S(i-1)=a, 而P(j-1)肯定=S(i-1),则P(0) = p(3)=p(j-1)=s(i-1)=s(6),这样就省略了一步比较,而i也不需要回退了。

KMP算法简单描述就是:匹配失败时,i不回退,j回退到某个位置,假设该位置为next(j) = next[j],
构造这样的一个next数组非常有意义,当next数组构造好后,我们的算法可以这样写:

 i = 0;
    j = 0;
    len = strlen(P);
    len_content = strlen(S);
    find_pos = 0;
    loop = 0;
    while ( i < len_content && j < len ) {
        loop++;
        if ( S[i] == P[j] ){
            if (j == 0) {
                find_pos = i;
            }
            i++;
            j++;
        } else {
            if ( next[j] == -1 ) {
                i++;
                j++;
            } else {
                j = next[j];
            }
        }
    }

 

这里有一个next[j] = -1,这里这样写,意思是说,如果next[j]==-1,那么表示S从i这个位置为起点的话,根本无法匹配P,那么S需要往前进一位, P回退到第一位。

上面的就是KMP算法,看出来它是以next数组为基础的,重点在于提前构造P的next数组

假设P=abaabcac
可以得知next(0) = -1,就是指第一个字符都无法匹配时,让i往前进1,让j重新归0。
next(i)的涵义实际上是让在字符串P0P1..P(i-1)中找到一个最大的k,让P0P1..P(k-1) = P(i-k-1)...P(i-1), 就是指在P0P1..P(i-1)的头部跟尾部找到一样的最长字符串,则next(i) = k。
使用数学归纳法的话,假设已经有next(i) =k, next(0) = -1, 那么
next(i+1) = ?
next(i) =k 推出P0P1..P(i-1)的头部跟尾部有一样的字符串,P0P1..P(k-1) = P(i-k-1)...P(i-1), 那么如果P(k)=P(i)的话,P0P1..P(k-1)P(K) = P(i-k-1)...P(i-1)P(i),则
next(i+1) = k+1 = next(i) +1,
但是如果很不幸, P(k)!= P(i),那么问题就转变成了类似于一开始的问题,从串S中搜索T:
T=P0P1..P(k-1)P(k),
S=P0P1..P(i-1)P(i),
当在S中搜索T时,在S的尾部找到了 P0P1..P(k-1) = P(i-k-1)...P(i-1), 但是最后一步,P(k) != P(i),匹配失败,于是运用KMP算法(这里很妙的是:KMP算法以求NEXT数组为关键,而求NEXT数组的过程中, 又继续运用了KMP算法跟递推的思想),
匹配失败时,让k =next(k), 继续比较P(k)跟p(i),如果仍然不等,继续让k=next(k),直到
P(k) = p(i)或者p(k) = -1为止。

那么整个的KMP算法如下:

#include "stdio.h"
#include "string.h"


int get_next(char* str_search, char* next) {
    int len;
    int i;
    int k;

    next[0] = -1;
    i = 0;
    len = strlen(str_search);
    k = -1;
   
    while ( i < len ) {
        if (str_search[i] == str_search[k] || k == -1) {
            i++;
            k++;
            next[i] = k;
        } else {
            k = next[k];
        }
    }
    return len;
}

void main() {
    char str_content[1024];
    char str_search[255];
    char next[255];
    int i;
    int j;
    int len;
    int len_content;
    int find_pos;
    int loop;
    scanf("%s", str_content);
    scanf("%s", str_search);

    //get the next
    len = get_next(str_search, next);

    //kmp
    i = 0;
    j = 0;
    len_content = strlen(str_content);
    find_pos = 0;
    loop = 0;
    while ( i < len_content && j < len ) {
        loop++;
        if ( str_content[i] == str_search[j] ){
            if (j == 0) {
                find_pos = i;
            }
            i++;
            j++;
        } else {
            if ( next[j] == -1 ) {
                i++;
                j = 0;
            } else {
                j = next[j];
            }
        }
    }
   
    if (j == len) {
        printf("found at %d, cost: %d loop/n", find_pos, loop);
    }   

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值