穿的模式匹配 : 字符串的定位工作
从 T="goodgoogle"中找到T="google"子串的位置。
朴素的模式匹配算法
主串的每一个字符作为子串开头,与要匹配的字符串进行匹配。对主串做大循环 i,字符T做小循环 j,直到匹配成功或者全部遍历结束。S[i] != S[j] 指针回退,重新匹配。 i = i-j+1 (回到档次匹配首位的下一个字符) j=0;
暴力匹配的思路,假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置,则有:
- 如果当前字符匹配成功(即S[i] == P[j]),则i++,j++,继续匹配下一个字符;
- 如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0。相当于每次匹配失败时,i 回溯,j 被置为0。
KMP模式匹配算法
大话数据结构是从T[1]开始的,然后一直晕着不知道该怎么办i , j 的值,会出现死循环。正解:初始化 i = -1,j = 0;感谢
https://www.jianshu.com/p/e2bd1ee482c3 的提示。
一切与书上就一一致了。注意产生index 的函数 跟 匹配 主副子串的 函数结构的相似性,但是并没有逻辑的相似性。
这里要深入理解 前缀、后缀 i, j 指针的意义。以及next 到底指的什么,不成功 子串指针回退的位置
package leetcode;
public class index_kmp {
public static void main(String[] args) {
// TODO 自动生成的方法存根
int index=0;
String S = "abcabababaababaaaaabaeaaab";
String T = "ababaababa";
index = index_find(S,T,0);
System.out.print(index);
}
//next数组记录的内容是:如果T[i]在匹配主字符串S时出现 S[m] != T[i];那么不需要T退到T[0]
//重新匹配(如果T中有重复子串的话) 节约匹配次数,next[i]表示 当前T[i] != S[m] 的话
//下一次的匹配是 S[m] 与 T[next[i]];;next[i]即为下次匹配的指针回退的位置,注意数组是否为从0开始
public static int[] next_find(String T){
int[] next = new int[T.length()];
next[0] = -1; //-1表示不存在相同的最大前缀和最大后缀
int i=-1;//前缀标记
int j=0;//后缀标记
while ( j < T.length()-1 ){ //注意 k的取值范围
if(i==-1 || T.charAt(i) == T.charAt(j) ) {
//当出现相同字符
//next[j++]=i++;替换接下来三句。下次j+1位置配失败时(前提是j位置已经成功了),指针会退到 i+1
//(因为i+1前的字符与j+1之前的k位一致) 处,
i++;
j++;//继续往后遍历 看看重复子串长是多少
next[j] = i;//下次j配失败时,指针会退到 i处,注意这里的j 与 i 都已经是本次循环+1了
}
else{
i = next[i];//遍历到字符不同的地方,前缀需要向前回溯了,回溯到
}
}
return next;
}
public static int index_find(String S,String T,int pos){
int i = pos;//pos指向 S的开始匹配位置
int j = 0;//指向T的匹配位置
int[] next = next_find(T);
while (i < S.length() && j < T.length()){
if(j==0 || S.charAt(i)==T.charAt(j)){
i++;
j++;
}
else
j = next[j];
}
if(j == T.length())
return i-j;//如果循环结束时,遍历到T的最后一个字符,并且成功(j == T.length),那么返回匹配S中子串的开始位置
else
return -1; //返回0,不用专门考虑i是否到了最后一个字符 依旧没有匹配成功的情况,包含在 else里面了
}
对KMP next数组求解的修改,如果 i++,j++ 之后的T[i] != T[j] 那么 j 回溯的位置就不仅仅到 next【j】也就是i 了,而是到 next【next[i]】 因为即便回溯到先前next位置,由于那个位置与当前位置的值一样,所以徒劳增加判断次数,因此要哦再往前回溯。直到当前值与回溯值不相等,才有真正回溯的必要。
修改 next_find函数 的while 循环。 注意,上一段代码中,设置next[0] = -1,这里 要嵌套一个判断
while ( j < T.length()-1){//注意 k的范围
if(i==-1 || T.charAt(i) == T.charAt(j) ) {//当出现相同字符
//next[j++]=i++;替换接下来三句。下次j+1位置配失败时(前提是j位置已经成功了),指针会退到 i+1 (因为i+1前的字符与j+1之前的k位一致) 处,
i++;
j++;//继续往后遍历 看看重复子串长是多少
if( T.charAt(i) != T.charAt(j) )
next[j] = i;//原来解法只有这个没有 else
else {
if(next[i]== -1){ //如果这里 不做判断 那么后面好多个a 对应的 next 都随着 第一个字符 初始化 的 -1 来走,会导致数组溢出的错误
next[i]=0;
}
next[j] = next[i]; //重点理解 本语句
}
}
else{
i = next[i];//遍历到字符不同的地方,前缀需要向前回溯了,回溯到
}
}