[进阶]——字符串——kmp算法

1.kmp算法

1.1.kmp的简单介绍

算法复杂度 O(n+m)

1.2. 实现原理

在这里插入图片描述

1.2.1.改进——相比于普通模式匹配的改进

每当一趟匹配过程中出现字符比较不等时, 不需要回溯i指针,而是利用已经得到的“部分匹配”的结果将模式串向右“滑动”尽可能远的一段距离后,继续进行比较。

个人:尽可能向右滑动。 就是为了尽量减少不必要的匹配,提高效率。

1.2.2.实现改进需要解决的问题

假设主串为"s1s2…sn", 模式串为“p1p2…pn”

为了实现改进算法, 需要解决下述问题:当匹配过程中产生“失配”(即si!=pj)时, 模式串“向右滑动”可行的距离多远。换句话说, 当主串第i个字符与模式串第j个字符“失配”时, 主串中第i个字符在不回溯的情况下应与模式串中哪个字符在比较?

假设与第k(k < j)个继续比较, 则模式串中前k-1个字符串必须满足下列关系, 并且不可能存在k‘ >k满足下列关系
’ p 1 p 2 . . . . p k − 1 ′ = ′ s i − k + 1 s i − k + 2 . . . s i − 1 ′ ’p_{1}p_{2}....p_{k-1} ' = 's_{i-k+1}s_{i-k+2}...s_{i-1}' p1p2....pk1=sik+1sik+2...si1
而已经得到的“部分匹配”结果
’ p j − k + 1 p j − k + 2 . . . . p j − 1 ′ = ′ s i − k + 1 s i − k + 2 . . . s i − 1 ′ ’p_{j-k+1}p_{j-k+2}....p_{j-1} ' = 's_{i-k+1}s_{i-k+2}...s_{i-1}' pjk+1pjk+2....pj1=sik+1sik+2...si1
因此,可得
’ p 1 p 2 . . . . p k − 1 ′ = ‘ p j − k + 1 p j − k + 2 . . . . p j − 1 ′ ’p_{1}p_{2}....p_{k-1} '=‘p_{j-k+1}p_{j-k+2}....p_{j-1} ' p1p2....pk1=pjk+1pjk+2....pj1
个人:从此可知, 与k继续比较,⇔ p[1j-1]串的,前缀[1k-1] = 后缀 ,并且是最大的相似前后缀


个人:由此, 将模式串每个字符失配后进行比较的位置记录下来则引入next函数。

若令next[j] = k,则next[j]表明当模式中第j个字符与主串中相应字符“失配”时, 在模式中需要重新和主串中该字符进行比较的字符位置。由此可孕畜模式串的next函数定义:

个人:next[j] = 0时, 就是第一个字符都失配,所以通过0来是他向右移动一位,

其他情况就是,最大相似前后缀为0, 即没有最大相似前后缀的情况。



1.2.3.在假设已知next函数时匹配过程如下

在匹配时有两种情况:

  • si = pj,则i和j增加1
  • 否则, i不变, j退到next[j]的位置。
    • 若相等则各自增加1
    • 否则, 退到next[next[j]]的位置,再比较,以此类推。
      • 这时会产生两种结果:
        • 退到某个next[…next[j]…]时, 字符比较相等, 各自加1
        • 否则j退到值为0, 此时模式串向右滑动一个位置

则kmp的代码如下

//该字符串下标从1开始, 下标0存的是字符串的长度,。
int Index_KMP(SString s, SString t, int pos){
    int i = pos,j = 1;
    while(i <= s[0]&&j<=t[0]){
        if(j==0|| s[i]==t[j]){
            i++;j++;
        }
        else j = next[j];
    }
    if (j>T[0])return i - T[0];
    else return 0;
}

1.2.3. 通过计算机语言实现next函数

前面我们可以通过定义手动计算出next函数的值。接下来要通过计算机语言来推出next函数。

上述可见,next函数只与模式串本省有关,与主串无关。我们可以分析其定义出发用递推的方法求得next函数值。

个人:递推, 就是从已知结果依次推出未知的结果

由定义可知

next[1] = 0

个人:此为最初的已知结果

设next[j] = k ,这表明在模式串中存在下列关系:
’ p 1 p 2 . . . . p k − 1 ′ = ‘ p j − k + 1 p j − k + 2 . . . . p j − 1 ′ ’p_{1}p_{2}....p_{k-1} '=‘p_{j-k+1}p_{j-k+2}....p_{j-1} ' p1p2....pk1=pjk+1pjk+2....pj1
其中k满足 1 < k < j , 并且不可能存在k’ > k 满足以上等式。此时next[j+1] = ?可能有两种情况:

个人:以下是由已知推未知的过程

  1. 若pk = pj, 则表明
    ’ p 1 p 2 . . . . p k ′ = ‘ p j − k + 1 p j − k + 2 . . . . p j ′ ’p_{1}p_{2}....p_{k} '=‘p_{j-k+1}p_{j-k+2}....p_{j} ' p1p2....pk=pjk+1pjk+2....pj
    并且不可能存在k‘ >k ,满足上述等式,所以next[j+1] = k+1,即
    n e x t [ j + 1 ] = n e x t [ j ] + 1 next[j+1] = next[j]+1 next[j+1]=next[j]+1

  2. 若pk≠pj, 则表明

    ’ p 1 p 2 . . . . p k ≠ ‘ p j − k + 1 p j − k + 2 . . . . p j ′ ’p_{1}p_{2}....p_{k} ≠‘p_{j-k+1}p_{j-k+2}....p_{j} ' p1p2....pk=pjk+1pjk+2....pj
    此时又可以把求next函数值, 看做成一个模式匹配问题。在该问题中,模式串即是主串又是模式串。所以当pk≠pj时, 下一个匹配的字符为next[k](设next[j] = x)。

    • 若px = pj, 则说明主串中第j+1个字符串之前存在一个长度为x的最长子串和模式串从首字母起长度为x的子串相等

    ’ p 1 p 2 . . . . p x ≠ ‘ p j − x + 1 p j − k + 2 . . . . p j ′ ’p_{1}p_{2}....p_{x} ≠‘p_{j-x+1}p_{j-k+2}....p_{j} ' p1p2....px=pjx+1pjk+2....pj

    所以,next[j+1] = next[k]+1

    • 若px!= pj, 则继续与next[x]比较, 依次类推。

      • 若存在next[…next[x]…]字符 = pj

        next[j+1] = …

      • 否则next[j+1] = 1

由此可得出, 递推球next函数的代码:

void get_next(SString T, int next[]){
    //求模式串T的next函数值, 存入数组next。
    int j = 1;// 初始为1表示第一个字符
    next[1] = 0;
 	k = 0;//第一个字符的k为0
    while(j < T[0]){
        if(k == 0 || next[k] == next[j]){
            next[++j] = ++k;
        }
        else k = next[k];
    }
}

1.2.4. 通过c实现的kmp算法

大多数语言字符串下标从0开始,所以我们以c为例实现kmp

void get_next(char* T, int next[]){
    int j = 0;
    next[0] = -1;
    k = -1;
    int len = strlen(T);
    while(j < len-1){
        if(k == -1|| next[k] = next[j]){
            next[++j] = ++next[k];
        }
        else k = next[k];
    }
}

void kmp(char * s, char * t, int pos){
    int i = pos;
    int j = 0;
    int slen = strlen(s);
    int tlen = strlen(t);
    while(i < slen && j < tlen){
 		if(j == -1||next[i] == next[j]){
            i++;
            j++;
 		}   
        else j = next[j];
    }
    if(j == tlen)return i - j; 
    else return -1;
}

参考

  • 严敏 吴伟民 编 数据结构(C语言版)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值