字符串匹配——暴力匹配+KMP

文章目录

  • 1. 暴力匹配算法
  • 2. KMP算法

1. 暴力匹配算法

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

字符串S a b b a a b b a a b a 匹配结果
模式串P a b b a a b a 不匹配,右移

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

字符串S a b b a a b b a a b a 匹配结果
模式串P a b b a a b a 不匹配,右移

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

字符串S a b b a a b b a a b a 匹配结果
模式串P a b b a a b a 不匹配,右移

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

字符串S a b b a a b b a a b a 匹配结果
模式串P a b b a a b a 不匹配,右移

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

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

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

字符串S a b b a a b b a a b a
模式串P a b b a a b a

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

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

a b b a a b b a a b a 说明
0 a的前后缀均为空
0 0 ab的前缀为a、后缀为b,相同前后缀为空
0 0 0 abb相同前后缀为空
0 0 0 0 abba相同前后缀为a,部分匹配值=1
0 0 0 0 1 abbaa相同前后缀为a,部分匹配值=1
0 0 0 0 1 2 abbaab相同前后缀为ab,部分匹配值=2
0 0 0 0 1 2 3 abbaabb相同前后缀为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;
}
  • 详细阅读,参考:从头到尾彻底理解KMP

你可能感兴趣的:(LeetCode算法分析)