问题:给定一个长字符串和一个目标字符串,匹配字符串,找出目标字符串第一次出现的位置
.
一、暴力匹配:
暴力匹配算法的思路很简单,用模式串的第一个字符和主串的第一个字符比较,如果相等就用模式串的第二个字符和主串的第二个字符比较,如果不相等就用子串的第一个字符和主串的第二个字符比较,以此类推:
public class 字符串暴力匹配 {
public static void main(String[] args) {
String st = "李怡嘉怡怡怡";
String su = "嘉怡";
int bao = bao(st, su);
System.out.println("起始下标在:"+bao);
}
/*
* @param str 长字符串
* @param stu 匹配字符串
* @date 2021/12/8 15:11
* @return void
*/
public static int bao(String chang, String duan) {
int lenchang = chang.length();//3
int lenduan = duan.length();//2
int i = 0;
while (i <= lenchang - lenduan) {
String str = chang.substring(i, i + lenduan);
if (str.equals(duan)) {
return i;
} else {
i++;
}
}
return -1;
}
}
暴力匹配算法的缺点很明显,效率实在是太低了,每一轮只能老老实实地把模式串右移一位,实际上做了很多无谓的比较.
而 KMP 算法的目标就是让模式串在每一轮尽量向后多移动几位,从而减少无谓的比较.
二、KMP算法:
KMP 算法(The Knuth-Morris-Pratt Algorithm)是一种改进的字符串匹配算法,由 D.E.Knuth,J.H.Morris 和 V.R.Pratt 提出,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP 算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个 next() 函数实现,函数本身包含了模式串的局部匹配信息。KMP 算法的时间复杂度 O(m+n)
整体思路:
1.先求next数组:例如:abab
--------------① 寻找前缀后缀最长公共元素长度
--------------② 求 next 数组
解释:next数组第一个往往是-1,因为此时没有前缀
next数组第二个往往是0,因为此时前缀是a,只有一个字符,所以没有相同前后缀
next数组第三个是0,因为此时前缀是ab,没有相同前后缀
next数组第四个是1,因为此时前缀是aba,有一个相同前后缀a
2.主方法:
第一步:模式串和主串的第一个等长子串比较,发现前 5 个字符都是匹配的,第 6 个字符不匹配;
失配时,模式串向右移动的位数为:已匹配字符数 - next(出错那个位数-1)
例如 这个应该移动 5-next(6-1)=5-3=2 移动后GT与GT对齐
代码:
import java.util.Arrays;
public class KMP算法 {
public static void main(String[] args) {
String str = "ATGTGAGCTGGTGTGTGCFAA";
String s = "GTGTGCF";
int kmp = kmp(str, s);
System.out.println("首次出现在下角标:" + kmp);
}
//KMP算法
public static int kmp(String str, String s) {
//前缀数组
int[] next = arry(s);
System.out.println("前缀数组:" + Arrays.toString(next));
for (int i = 0; i <= str.length() - s.length(); ) {
int temp = 0;
for (int j = i; j < i + s.length(); j++) {
//如果有出错
if (!(str.charAt(j) == s.charAt(j - i))) {
temp = j - i + 1;//出错的是哪个字符
break;
}
}
if (temp == 0) {//如果temp值为0没有发生变化,说明全部匹配上
return i;
} else {
if (next[temp - 1] != -1) {//如果出错的不是第一个字符,需要计算右移几位
i += temp - 1 - next[temp - 1];
} else {//如果出错的是第一个字符,直接比对下一个字符
i++;
}
}
}
return -1;
}
//匹配前缀数组
public static int[] arry(String s) {
int[] arr = new int[s.length()];
arr[0] = -1;
arr[1] = 0;
for (int i = 1; i < s.length()-1; i++) {
for (int j = 0; j < i; j++) {
if (s.substring(0, j+1).equals(s.substring(i - j, i+1))) {
arr[i+1] = j+1;
}
}
}
return arr;
}
}