针对字符串的模式匹配问题,解决的算法有挺多的,下面就把我所知道的一一介绍吧。
第一种,朴素模式匹配(时间复杂度为O(mn))
之所以把这种算法称作朴素模式匹配,我想大概是因为这个算法可以看做是在通过遍历,从而完成匹配,这种方式应该是比较朴素的。(个人看法)
举例来说吧。
主串s = “abcdabcde”,子串t = “abcde”
(1)从主串第一个字符开始,和子串中的字符进行逐一比较,可以发现前四个字符都匹配成功,而第四个没有匹配成功;
(2)主串的开始下标+1,然后再继续跟子串匹配(子串从0下标开始);
(3)没有匹配成功,就重复第二步,直到全部匹配成功,或者说主串s已到达末尾。
实现代码:
private static int simple(String s, String t) {
int i = 0;// 记录主串下标
int j = 0;// 记录子串下标
while (i < s.length() && j < t.length()) {
if (s.charAt(i) == t.charAt(j)) {
// 当前位置字符匹配成功
i++;// 主串下标+1
j++;// 子串下标+1
} else {
// 当前位置字符匹配不成功
i = i - j + 1;//i-j:主串回到匹配初始位置,+1:主串下标+1
j = 0;// 子串下标置零
}
}
// 匹配成功,返回模式串p在文本串s中的位置,否则返回-1
if (j == t.length())
return i - j;
else
return -1;
}
第二种,快速模式匹配(时间复杂度为O(n))
快速模式匹配算法,实现了保持主串的检索位置不懂,而移动子串的检索位置,省去了一些不必要的操作,提高了效率。而要想确定子串的检索位置应该怎么移动,就需要了解几个概念:前缀、后缀。举个例子来说吧,给定字符串“ABCDABD”,
也就是说,原模式串子串对应的各个前缀后缀的公共元素的最大长度表为:
这个表其实就是next数组的前身,把表中数据都往后移一位,并在首位填上-1,即:
其实整个KMP算法的实现,我觉得关键就在于如何获得next数组,next数组其实就是查找模式串中每一位前面的子串的前后缀有多少位匹配,从而决定在失配时子串检索位置应该回退到哪个位置,那接下来就来推敲一下,如何获得next数组吧。
由于我个人的表达能力有限,建议参考http://www.cnblogs.com/tangzhengyue/p/4315393.html,讲的很清楚,通俗易懂。
代码实现:
public static int[] getNext(String t) {
int[] next = new int[t.length()];
next[0] = -1;
int j = 0;
int k = -1;
while (j < t.length() - 1) {
if (k == -1 || t.charAt(j) == t.charAt(k)) {
next[++j] = ++k;
} else {
k = next[k];
}
}
return next;
}
public static int KMP(String s, String t) {
int i = 0;
int j = 0;
int[] next = getNext(t);
while (i < s.length() && j < t.length()) {
if (j == -1 || s.charAt(i) == t.charAt(j)) { // 当j为-1时,要移动的是i,当然j也要归0
i++;
j++;
} else {
// i不需要回溯了
j = next[j]; // j回到指定位置
}
}
if (j == t.length()) {
return i - j;
} else {
return -1;
}
}
第三种,Boyer-Moore 算法,简称 BM 算法。
BM 算法定义了两个规则:
- 坏字符规则:当文本串中的某个字符跟模式串的某个字符不匹配时,我们称文本串中的这个失配字符为坏字符,此时模式串需要向右移动,移动的位数 = 坏字符在模式串中的位置 - 坏字符在模式串中最右出现的位置。此外,如果"坏字符"不包含在模式串之中,则最右出现位置为 -1。
- 好后缀规则:当字符失配时,后移位数 = 好后缀在模式串中的位置 - 好后缀在模式串上一次出现的位置,且如果好后缀在模式串中没有再次出现,则为 -1。
- 如果该字符没有在模式串中出现则直接跳过,即移动位数 = 匹配串长度 + 1;
- 否则,其移动位数 = 模式串中最右端的该字符到末尾的距离 +1。