KMP算法

KMP算法

KMP算法是用于解决字符串匹配的经典算法,就是判断一个字符串是否是另一个字符串的子串。

例如现在我们有两个字符串,字符串s: "aabaabaaf"和字符串t: “aabaaf”,我们该如何判断字符串t是否是字符串s的一个子串呢?如果使用暴力匹配,即两个for循环,暴力的时间复杂度是两个字符串长度的乘积。

而KMP算法有什么不同呢:

就比如字符串s和t的匹配中,当我们匹配到第六个字符时,发现b和f并不匹配,此时按照暴力法我们应该将s的下标+1,继续从t的开头开始匹配;但是如果是KMP算法,就会保持s的下标不变,将t的下标回退到2,即第三个字符’b’,这样是不是效率就提高了。

明白了KMP算法的优越性,我们想知道为什么它可以这样回退,回退的规则又是什么。其实,我们看字符串t,这里存在着相同的前后缀"aa",正是因为有了相同的前后缀,我们就可以省去再次匹配相同字符的过程。那为什么t的下标回退到的位置2是怎么得来的呢?这就要用到字符串t的前缀表了。

前缀表

所谓前缀表,就是一个字符串中相同前后缀的长度所组成的表。

对于字符串t:

前缀包括:a, aa, aab, aaba, aabaa (从前往后不包含尾字符)

后缀包括:f, af, aaf, baaf, abaaf (从后向前不包含首字符)

a ----------不存在相同前后缀,0

aa --------“a”, 1

aab -------不存在,0

aaba ------“a”, 1

aabaa ---- “aa”, 2

aabaaf —不存在,0

所以字符串t的前缀表为{0,1,0,1,2,0}

对于这个前缀表,当匹配到字符’f’时,退回的位置要看前缀表中它的前一位。

也可以将前缀表整体右移,变成{-1,0,1,0,1,2},这样退回的位置就取决于字符本身对应的那一位了。

也可以将前缀表整体-1,这样退回的位置也是字符本身对应的那一位了。它们的本质都是利用字符串的前缀表找到回退的位置,只是实现起来有略微的不同,我们就以原本的前缀表为例进行演示。

next数组

next数组其实就是前缀表在我们实际代码中的存在形式,这里我们要讲的是如何通过一个字符串找到它的前缀表,即next数组。找next数组并不难,只要记住四步操作:初始化,前后缀不相等,前后缀相等,赋值。

  • 初始化

    首先要定义两个下标i , j,用来标识前后缀对应的字符;还要赋值next[0]=0;因为下标为0时无法再回退了。

  • 前后缀不相等

    前后缀不相等时,我们就需要回退,即j = next[j - 1]。同时还要保证j > 0,同样因为j = 0时无法再回退。

  • 前后缀相等

    前后缀相等时,我们就继续匹配,j++。

  • 赋值

    给next数组赋值,此时相同前后缀的长度对应j的大小,即next[i] = j。

代码实现:

void getNext(int *next, string t){
    //初始化
    int i = 1, j = 0;
    next[0] = 0;
    for (; i < t.size(); i++) {
    	//前后缀不相等,注意回退是一直回退到可以匹配的位置,所以是while
        while (j > 0 && s[i] != s[j]) {
        	j = next[j - 1];
    	}
    	if (s[i] == s[j]) { //前后缀相等
        	j++;
    	}
    	next[i] = j; //赋值
    }
}

例题实现:

函数实现的过程与寻找next数组大体一致:

bool isSonString(string s, string t) {
    int next[s.size()];
    getNext(next, t);
    int i = 0, j = 0; //i = 0,从头开始匹配
    for (; i < s.size(); i++) {
        while (j > 0 && s[i] != t[j]) {
            j = next[j - 1];
        }
        if (s[i] == t[j]) {
            j++;
        }
        if (j >= t.size()) { //字符串t匹配完
            return true;
        }
    }
    return false;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值