数据结构与算法——字符串匹配之暴力匹配+KMP

1. 暴力匹配算法

i=0、j=0,S.charAt(i)==P.charAt(j)、i++、j++,直到i=6、j=6、不再相等

字符串Sabbaabbaaba匹配结果
模式串Pabbaaba不匹配,右移

回退i,i=1、j=0,字符不相等

字符串Sabbaabbaaba匹配结果
模式串Pabbaaba不匹配,右移

i=2、j=0,字符不相等

字符串Sabbaabbaaba匹配结果
模式串Pabbaaba不匹配,右移

i=3、j=0,i=4、j=1、不相等

字符串Sabbaabbaaba匹配结果
模式串Pabbaaba不匹配,右移

回退i,i=4、j=0,i=5、j=1,,,直到i=10、j=7,匹配完成

字符串Sabbaabbaaba匹配结果
模式串Pabbaaba匹配,不移动
  • 存在的问题:如果模式串与字符串的前几个字符都匹配,但下一个字符不匹配,则需要回退i,浪费时间
  • 解决思路:固定字符串S,只移动模式串P

以第一次匹配为例,可以发现字符串S的5、6位与模式串P的前2位相同。在i=6、j=6时,匹配失败,可将模式串P向右移动4位,从i=6、j=2继续开始匹配,保证了i不回退

字符串Sabbaabbaaba
模式串Pabbaaba

2. KMP算法

  • 引入2个概念

    • 前缀:从首字符开始的子串,不包含尾字符,abcde前缀:a、ab、abc、abcd
    • 后缀:从尾字符开始的子串,不包含首字符,abcde后缀:e、de、cde、bcde
  • KMP需要找到字符串中最长的相同前后缀

    • 比如:ababa
    • 前缀有:a、ab、aba、abab
    • 后缀有:a、ba、aba、baba
    • 相同的前后缀:a、aba
    • 最长的相同前后缀:aba
  • 部分匹配值:最长的相同前后缀的长度,ababa的部分匹配值 = 3

  • 部分匹配表:依据部分匹配值

abbaabbaaba说明
0a的前后缀均为空
00ab的前缀为a、后缀为b,相同前后缀为空
000abb相同前后缀为空
0000abba相同前后缀为a,部分匹配值=1
00001abbaa相同前后缀为a,部分匹配值=1
000012abbaab相同前后缀为ab,部分匹配值=2
0000123abbaabb相同前后缀为abb,部分匹配值=3
  • 代码实现

步骤1:计算模式串的部分匹配表

//获取到一个字符串(子串) 的部分匹配值表
public static int[] kmpNext(String dest) {
	//创建一个next 数组保存部分匹配值
	int[] next = new int[dest.length()];
	next[0] = 0; //如果字符串是长度为1 部分匹配值就是0
	for(int i = 1, j = 0; i < dest.length(); i++) {
		//当dest.charAt(i) != dest.charAt(j) ,我们需要从next[j-1]获取新的j
		//直到我们发现 有  dest.charAt(i) == dest.charAt(j)成立才退出
		//这是kmp算法的核心点
		while(j > 0 && dest.charAt(i) != dest.charAt(j)) {
			j = next[j-1];
		}
		
		//当dest.charAt(i) == dest.charAt(j) 满足时,部分匹配值就是j+1
		if(dest.charAt(i) == dest.charAt(j)) {
			j++;
		}
		next[i] = j;
	}
	return next;
}
//测试
public static void main(String[] args) {
	int[] next=kmpNext("AAA");	
	System.out.println("next="+Arrays.toString(next));	
}
//输出结果:next=[0, 1, 2]

步骤二:在遍历匹配中依据部分匹配表来右移模式串,失配时,模式串向右移动的位数为:已匹配字符数 - 失配字符的上一位字符所对应的部分匹配值

/**
 * @param str1 源字符串
 * @param str2 子串
 * @param next 部分匹配表, 是子串对应的部分匹配表
 * @return 如果是-1就是没有匹配到,否则返回第一个匹配的位置
 */
public static int kmpSearch(String str1, String str2, int[] next) {
	
	//遍历 
	for(int i = 0, j = 0; i < str1.length(); i++) {
		
		//需要处理 str1.charAt(i) != str2.charAt(j), 去调整j的大小
		//KMP算法核心点, 可以验证...
		while( j > 0 && str1.charAt(i) != str2.charAt(j)) {
			j = next[j-1]; 
		}
		
		if(str1.charAt(i) == str2.charAt(j)) {
			j++;
		}			
		if(j == str2.length()) {//找到了 // j = 3 i 
			return i - j + 1;
		}
	}
	return  -1;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值