给定一个字符串和一个文本,在文本中找出这个字符串的位置。
最直接的解法是暴力遍历字符串。时间复杂度是o(n*m)
暴力解法:
字符串,文本从头开始一个一个字符串匹配,当字符串中和文本中字符不匹配,开始回溯到文本这一个开始比较位置的下一个字符和字符串开始字符串比较。知道遍历完文本,或者找出匹配的位置。
暴力解法时间复杂度高。
下面介绍字符串匹配KMP算法。
KMP算法是经典的字符串匹配算法,它的时间复杂度是O(n), 核心思想是回溯移动比较字符串索引,不移动匹配字符串的索引。一般情况,匹配的字符串很长,远远大于比较字符串的长度。
和暴力算法不同,KMP算法额外生成一个next数组,这个数组就是 字符串不匹配时,下一次比较字符的索引位置,而不移动回溯匹配字符串位置。大大降低了时间复杂度。
先来看一下草图:(next数组根据比较字符串计算出来的)
如图,索引为4时B和E不匹配,根据KMP的思想,来看下一步怎么比较的。 如下图所示
如上图直接移动的"ABABE"匹配字符串的索引,在比较。移动的规则在于next数组,当上一步索引为4是不匹配,求next[4] = 2,
索引直接设置"ABABE"的索引为2,在和索引为4 的比较字符串匹配。
就这样直到字符串匹配成功为止。上面匹配开始索引为12
分解步骤求"ABABE"的next数组:
next数组的求解,索引0,1初始为-1,0,cn = 0。
索引为index = 2时,比较索引index - 1 和cn 位置字符串,设置为cn。
索引index = 3 时, 比较索引index - 1 和cn 位置字符串,相等cn = cn + 1 ,设置next[index] = cn
索引index = 4 时,比较索引index-1和cn位置字符串,相等cn = cn + 1, 设置next[index] = cn
代码:
public int[] getNextArray(char[] ms) {
if (ms.length == 1) {
return new int[] { -1 };
}
int[] next = new int[ms.length];
next[0] = -1;
next[1] = 0;
int pos = 2;
int cn = 0;
while (pos < next.length) {
if (ms[pos - 1] == ms[cn]) {
next[pos++] = ++cn;
} else if (cn > 0) { //调整cn ,如果不加判断,那么next数组后面都趋向一个数
cn = next[cn];
} else {
next[pos++] = cn;
}
}
return next;
}
思想是求不匹配字符前的首尾相同字符串的长度。
KMP算法代码:
public int getIndexOf(String s, String m) {
if (s == null || m == null || m.length() < 1 || s.length() < m.length()) {
return -1;
}
char[] ss = s.toCharArray();
char[] ms = m.toCharArray();
int si = 0;
int mi = 0;
int[] next = getNextArray(ms);
while (si < ss.length && mi < ms.length) {
if (ss[si] == ms[mi]) {
si++;
mi++;
} else if (next[mi] == -1) {
si++;
} else {
mi = next[mi];
}
}
return mi == ms.length ? si - mi : -1;
}