KMP算法使用环境
当我们进行字符串匹配的时候,暴力匹配是最容易想到的方法。但是由于暴力匹配需要将模式串的每一位都和主串进行匹配,所以时间复杂度高达O(mn)。其中m为主串长度,n为模式串的长度。在主串与模式串长度都比较大时,暴力匹配的性能会变得很差。这个时候需要使用新的方法来进行字符串匹配,KMP算法应运而生。
KMP算法基本原理
KMP算法主要是消除无用的匹配,设主串为ABBABAABABBAA,模式串为BABBA。引入三个概念,最大前缀、最大后缀和最大前缀后缀公共元素长度。例如:模式串BABBA的最大前缀是BABB,最大后缀是ABBA,最大前缀后缀公共元素的计算是前缀从前往后,后缀从后往前。例子中的最大前缀后缀公共元素是BA,长度为2。
匹配过程
当模式串与主串不匹配的时候,需要重新定位匹配的位置。如下图,如果是暴力匹配的话则需要把模式串右移一位,从模式串第一位和主串第四位进行匹配。而KMP存在前缀后缀公共元素。可以把前缀移动到后缀所在的位置,而后从前缀的后一位开始匹配,即模式串的第二位与主串的第六位进行匹配。这种匹配方式在匹配过程中只需要匹配整个主串即可。所以时间复杂度是O(m),但这个并不是这个算法的整体复杂度。这个算法需要生成一个数组,长度为模式串长度。
next数组
用来生成next数组的表格
| 模式串字串 | 最大前缀 | 最大后缀 | 最大前缀后缀公共元素 | 最大前缀后缀公共元素长度 |
|---|---|---|---|---|
| B | 空 | 空 | 空 | 0 |
| BA | B | A | 空 | 0 |
| BAB | BA | AB | B | 1 |
| BABB | BAB | ABB | B | 1 |
| BABBA | BABB | ABBA | BA | 2 |
最大前缀后缀公共元素长度数组
| B | A | B | B | A |
|---|---|---|---|---|
| 0 | 1 | 2 | 3 | 4 |
| 0 | 0 | 1 | 1 | 2 |
next数组
| B | A | B | B | A |
|---|---|---|---|---|
| 0 | 1 | 2 | 3 | 4 |
| -1 | 0 | 0 | 1 | 1 |
private final static int ZERO = 0;
private final static int ONE = 1;
private final static int DEFAULT = -1;
public static void main(String[] args) {
String mainStr = "ABBABAABABBAA";
String pattenStr = "BABBA";
KmpStandard kmpStandard = new KmpStandard();
int ans = kmpStandard.kpmCompare(mainStr,pattenStr);
if(ans == -1){
System.out.println("没有匹配到字符串");
}else{
System.out.println("从第" + ans + "位匹配到字符串");
}
}
使用next数组进行字符串匹配
public int kpmCompare(String mainStr,String pattenStr){
byte[] bMain = mainStr.getBytes();
byte[] bPatten = pattenStr.getBytes();
int length = bPatten.length;
int mainLength = bMain.length;
//构建数组
int[] temp = new int[length];
int[] next = new int[length];
//默认第一位为0
next[ZERO] = ZERO;
//设置指针
int jndex = ZERO;
int index = ONE;
//数组赋值
for(;index < length;index++){
if(bPatten[index] == bPatten[jndex]){
temp[index] = ++jndex;
}else if(jndex == ZERO){
temp[index] = ZERO;
}else{
while(jndex > ZERO) {
jndex = temp[jndex - 1];
if (bPatten[jndex] == bPatten[index]) {
temp[index] = ++jndex;
break;
}
}
if(jndex == ZERO) {
temp[index] = ZERO;
}
}
}
//生成next数组
int t = ONE;
next[ZERO] = DEFAULT;
for(;t < length;t++){
next[t] = temp[t-ONE];
}
//进行比较
int patten = ZERO;
for(int match = ZERO;match < mainLength;){
if(bMain[match] == bPatten[patten]){
match++;
patten++;
}else if(next[patten] == DEFAULT){
match++;
}else{
patten = next[patten];
}
//判断是否匹配到出口
if(patten == length){
return match - patten + ONE;
}
}
return DEFAULT;
}
使用最大前缀后缀公共元素长度数组进行匹配
public int kpmCompare(String mainStr,String pattenStr){
byte[] bMain = mainStr.getBytes();
byte[] bPatten = pattenStr.getBytes();
int length = bPatten.length;
int mainLength = bMain.length;
//构建数组
int[] next = new int[length];
//默认第一位为0
next[ZERO] = ZERO;
//设置指针
int jndex = ZERO;
int index = ONE;
//数组赋值
for(;index < length;index++){
if(bPatten[index] == bPatten[jndex]){
next[index] = ++jndex;
}else if(jndex == ZERO){
next[index] = ZERO;
}else{
while(jndex > ZERO) {
jndex = next[jndex - 1];
if (bPatten[jndex] == bPatten[index]) {
next[index] = ++jndex;
break;
}
}
if(jndex == ZERO) {
next[index] = ZERO;
}
}
}
//进行比较
int patten = ZERO;
for(int match = ZERO;match < mainLength;){
if(bMain[match] == bPatten[patten]){
match++;
patten++;
}else if(patten == ZERO){
match++;
}else{
patten = next[patten];
}
//判断是否匹配到出口
if(patten == length){
return match - patten + ONE;
}
}
return DEFAULT;
}
记录下学习历程,实时补充