共计 2688 个字符,预计需要花费 7 分钟才能阅读完成。
KMP 算法次要是在肯定长度的字符串中疾速匹配出所需的指标字符串,也称模式字串,最大特点就是考究一个快字。
个别是实用于字符串进行比对或者匹配的场景之下,根本概括为在字符串不匹配,需进行下一次匹配时,利用已知的已匹配的字符串(文本内容)防止从头开始匹配带来的节约。
如果是暴力解决的话,步骤如下:
- 定义头指针以及字符指针,别离指向定长字符串最左端、模式字符串最左端
- 顺次对字符进行比拟,
- 发现不匹配字符,回溯指针到指针开始匹配的下一位
- 反复第二步,直至找到对应字符串,即模式字符串
使用暴力解法,效率低下的次要起因即是指针回溯的地位不失当,反复匹配操作。
前置概念
最长公共前后缀
所谓前缀、后缀的区别,简略就是说以某结点区域为宰割,前一部分(不蕴含最初一个字符)就为前缀,后一(不蕴含匹配失败的局部以及第一个字符)局部为后缀。
在了解最长公共前后缀之前,得分明公共前后缀的概念。公共前后缀简略阐明,可认为是模式字符串的前某局部字符与后某字符两头局部为结点宰割的区域。
最长公共前后缀就是在公共前后缀的根底上,寻找最长的局部(即便可能等于其自身,也不取其为最长,无奈进行挪动,故选其自身少一位为最长),即便是没有结点宰割区域也可。
示例图的公共前后缀为 ABAB,而非 ABABA
前缀表
前缀表就是保留前缀局部的长度信息及其模式字符串的对应字符地位,以便产生不匹配状况之时,通过对前缀表的内容,可能给出从新开始匹配字符串的地位信息,简略说就是匹配失败,回退字符串,从新开始。
该表中长度代表公共前后缀长度,next 代表下一次匹配时,模式字符串开始的地位,为下标值。此处咱们能够晓得其公共前后缀长度与 next 的值统一。
当然,对于 next 数组的值也有多种认识,有时候会将模式字符串的下标从零开始,即整体向右移一位,在制作 next 数组时,合乎通常思维,第几位对应下标值为几。
实现 KMP 算法须要进行已匹配字符串的区域切割,保障不做反复的操作,而这就与前缀表的应用不可拆散。
算法思路
简略 KMP 算法思路
- 定义头指针以及字符指针,别离指向定长字符串最左端、模式字符串最左端
- 开始对字符顺次匹配,直至匹配失败的字符地位进行
- 对已匹配的模式字符串寻找最长公共前后缀字符串
- 若不能找到, 间接挪动字符指针到模式字符串最左端,从新开始匹配
- 若能找到,将公共前后缀中的前缀局部后移到后缀局部,
- 此时,字符指针就从本来指向后缀局部下一位指向前缀局部下一位
- 模式字符串长度长于残余字符串的长度,匹配失败,
- 否则从第二、三步持续开始匹配
- 最初,找到字符或者匹配失败
如下即为无最长前后缀的状况
利用 next 表优化
- 先对模式字串进行每个字符的最长公共前缀长度进行计算并进行解决,再存进对应数组,即为 next 数组
- 定义指针,开始匹配
- 当遇到不匹配时,读取以后字符的 next 数组对应值,作为字符指针的起始地位,从新从第二步开始
- 直至匹配胜利,或模式字符串长度长于残余字符长度,即匹配失败
算法代码
结构 next 数组
-
初始化
-
定义两个指针。
- 此时,起始默认前后缀为模式字符串第一个字符,长度为 1,
- 第一个指针 frist 指向前缀最初一个元素,即 length – 1 初始化为 0,可知前缀为 [0,first);
- 第二个指针 last 指向后缀最初一个元素,初始化为 1 即为 length,可知后缀为 (first,last]。
- frist 指针在完结之时指向的地位即为 next 数组的对应值
- 初始化 next 数组长度为模式字符串的长度减一,再初始化 next[0] = 0;
-
-
解决前后缀不同的状况
- string[frist] != string[last] , 屡次回退为 while 并且为保障 frist-1 不越界,判断条件必须为 frist > 0 && string[frist] != string[last]
- 如果开端字符不相等,屡次回退到前一个字符的 next 数组对应值,即 frist = next[frist-1]
-
解决前后缀雷同的状况
- string[frist] == string[last]
- first ++
-
更新 next 数组的值
- 每一次 last 迭代,即 last ++ 前
- 记录此时 next 对应值: next[last] = first;
最终代码
//target 为模式字符串,string 为定长字符串
// 当匹配胜利时,返回在定长字符串中的与模式字符串统一的字符串起始下标
// 匹配失败返回 -1
public static void getNext(int[] next,char[] target){
int frist = 0;
next[0] = 0;
for(int last = 1;last < target.length; last++){while(frist > 0 && target[frist] != target[last]){frist = next[frist-1];
}
if(target[frist] == target[last]){frist++;}
next[last] = frist;
}
}
public static int getFristStr(char[] target,char[] string){if(target.length == 0){return 0;}
int next[] = new int[target.length];
getNext(next,target);
int targetPointer = 0;
for(int stringPointer = 0; stringPointer < string.length; stringPointer++){while(targetPointer > 0 && target[targetPointer] != string[stringPointer]){targetPointer = next[targetPointer-1];
}
if(target[targetPointer] == string[stringPointer]){targetPointer++;}
if(targetPointer == target.length){return (stringPointer - target.length + 1);
}
}
return -1;
}
public static void main(String[] args){Scanner input = new Scanner(System.in);
String str1 = input.next();
String str2 = input.next();
char[] string = str1.toCharArray();
char[] target = str2.toCharArray();
System.out.println(getFristStr(target,string));
}