这里写自定义目录标题
KMP
高效的字符串匹配算法
找出p在s中出现的所有位置
算法实质
假如s字符串是123456789(数字代表位置而非内容)
t字符串是123456
假设我们12345匹配成功而6没成功,在12345中最长的boder(boder就是即是前缀也是后缀的串)是1和5,那么如果第一次匹配失败
也就是t的1对应s的1失败,第二次就让t的1对应s的5开始匹配,这样是最快而且不会漏
证明:如果在5之前有能匹配的字符串我们假设4也能匹配那么当t往后匹配就应该匹配到4,那就有一个boder是45,比5长,这与5是最长的boder矛盾
前缀后缀
不包含本身
比如1.abcd的前缀是a,ab,abc后缀是d,cd,bcd
2.abcd当中c的前缀后缀指的是c前面的字符串的前后缀也就是ab的前后缀
3.abcdabd当中a的前后缀是求abcd的前后缀
LPS
相同的前后缀里面最长的相同前后缀的长度
LPS数组是字符串中每一个字符的LPS(可以判断出任意字符串的LPS数组前两个值一定是0
KMP应用LPS数组
t串的LPS数组已经求出
比如t串为123456,假如匹配1-5成功到6不成功,就找t的LPS数组对应5的LPS,假设是3,那就跳过三个字符继续匹配
next数组
实际上是把LPS数组整体后移一位,前面补-1,这样KMP的时候可以直接用对应位置的lps
KMP应用next数组实例
import java.util.ArrayList;
import java.util.Scanner;
public class KMP {
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
String fatherInput=sc.next();
String sonInput=sc.next();
ArrayList<Integer> list= KMPMethod(fatherInput,sonInput);
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
public static ArrayList<Integer> KMPMethod(String fatherInput, String sonInput){
ArrayList<Integer> list = new ArrayList<>();
//定义一个整型数组存放已经求出来的next数组
int [] next = getNext(sonInput);
int i = 0,j=0;
while(i<fatherInput.length()){
if(fatherInput.charAt(i)==sonInput.charAt(j)){
i++;
j++;
} else if (j>0) {
//在j位置匹配不上的时候就把next[j]赋给j也就是跳过前j个元素直接从下标为j的元素开始匹配
j=next[j];
} else {
//j位置配不上且j=0说明子串第一个和父串第一个不一样,那就找父串第二个和子串第一个匹配直到匹配上
i++;
}
//如果没有完全匹配就会一直从父串前面的部分一直匹配j就一直小于子串长度,直到子串长度和j一样说明匹配了子串长度个字符都相同说明这段和子串完全相同
if(j==sonInput.length()){
list.add(i-j);
j=0;
}
}
return list;
}
//一个求next数组的方法
public static int [] getNext(String sonInput){
int [] next=new int[sonInput.length()];
//lps数组定义为lpss的第一个由于只有一个字母,没有前后缀所以他的lps是0
next[0]=-1;
next[1]=0;
int i=2;
//这个用来代指当前共同前后缀的长度
int lps=0;
while(i< sonInput.length()){
//意思是如果当前第lps个字符如果和第i-1个字符相等说明上一个位置往后加上后面那一位仍然是相同的前后缀
/*举个例子:假如0123456789 10 11 12 13 14 15 16 17 18(数字指代位置而非内容),假设位置16的lps也就是0-15串的lps是4,也就意味着0123和12,13,14,15相等,
当指针位置到17的时候,此时i-1变成了16,比较4和16位置是否相等
1.如果相等,就说明最长相同前后缀是01234和12,13,14,15,16,那么17的lps也就是0-16的lps就是5,所以lps++(注意因为每次移动
一个位置所以lps肯定是+1不可能是别的值,然后把lps赋值给next数组对应的下标17的位置,也就意味着下标17位置的lps就是5*/
if(sonInput.charAt(lps)== sonInput.charAt(i-1)){
//注意这里是先拿lps当下标比较,但lps要赋值给next数组时候要先+1因为这时候lps代表的是数目而不是下标
lps++;
next[i]=lps;
i++;
}else if (lps==0){//如果对应的两个不相等而lps又是0也就意味着上一个位置的lps也是0,那这个位置的lps也是0
next[i]=0;
i++;
}else{
/*接上面第一种情况
2.如果不相等,也就是4和16不相等,由于在0-16串中0123仍然是前缀但12,13,14,15此时并不是后缀,所以不能直接认 为lps是4,这时候我们发现
由于0123和12,13,14,15是一样的,所以把next[lps-1]赋值给lps,由于此时的lps是4,next[lps-1]就是下标3 位置的lps,i仍然是17,也就是新一轮循环是检查下标3前面的串的相同前后缀最大的相同串与下标16是否相等,比如, 假如下标3位置的lps为2也就是012串的最大相同前后缀长度为2也是就是01和12相等,那么就是比较下标2和下标16是 否相等,这里要解释一下为什么比下标2,因为0123和12,13,14,15是一样的,那么01和12相等实际上相当于01和 14,15相等,这时候再往后比较就是比较2和16,如果相等012是前缀,14,15,16是后缀,这就是17位置的lps*/
lps=next[lps-1];
}
}
return next;
}
}