【数据结构与算法】字符串匹配 - 暴力算法
通过CTRL + F
查找文本这种操作相信每个人都有,在一段文本中查找另外一段文本,这个操作就叫做字符串的模式匹配,比如在AABABCDEF
中查找ABCD
,那么AABABCDEF
被叫做文本串(也叫主串),ABCD
被叫做为模式串,通常主串的长度使用N
来表示,模式串的长度使用M
来表示,上面两个串的匹配情况如下图所示:
正如上图绿色部分那样,文本串中包含模式串,结果就是匹配到了。
字符串模式匹配的算法有很多,比如暴力算法、KMP算法、BM算法、BK算法等,一一来介绍。
BF算法
BF算法也叫做暴力算法,思路简单,很容易理解,首先将文本串和模式串左对齐,如果出现不匹配的情况,则模式串整体向后移动一位,否则就匹配了,依然拿下图来说明:
两个串左对齐开始匹配,如下图:
红色处字符不相等,则停止比较后面的字符,将整个模式串向后移动,再次匹配:
红色处字符也不相等,则继续向后移动模式串,重新匹配:
红色处字符也不相等,则继续向后移动模式串,重新匹配:
此时找到了匹配结果。
通过上面的图解分析,其实能够很好地总结出匹配的步骤:
- 循环文本串,文本串中的当前字符(当前指针指向的字符)开始向后以此与文本串比较。
- 比较的时候,如果遇到字符不相等的情况,那么就需要停止比较,文本串指针需要向后移动一位,继续与模式串比较(从模式串头开始以此比较)
- 如果与模式串的所有字符以此比较都相等,那么说明匹配到了。
按照上面的分析,我可以定义一个指针i
指向文本串,定义一个指针j
指向模式串:
第一次匹配:
本次匹配,以为i = 0, j = 0
开始匹配,当j = 1
的时候字符不匹配(text[i + j] != pattern[j]
),也就是text[1] != pattern[1]
,接下来指针i
要向后移一位,而指针j
则需要设置为0
,继续匹配。
第二次匹配:
匹配以i = 1
开始,当j = 2
的时候字符不匹配text[i + j] != pattern[j]
,也就是text[3] != pattern[2]
,此时指针i
需要继续向后移动一位,指针j
还是回到头部
第三次匹配:
匹配以i = 2
开始,当j = 0
的时候字符不匹配text[i + j] != pattern[j]
,也就是text[2] != pattern[0]
,此时指针i
需要继续向后移动一位,指针j
还是回到头部
第三次匹配:
匹配以i = 3
开始,找到了模式串,但是j
还是会向后移动的,此时j
等于模式串的长度m
。
代码如下:
class Solution {
public int strStr(String text, String pattern) {
// 文本串长度为n
int n = text.length();
// 模式串长度为m
int m = pattern.length();
for (int i = 0; i < n - m; i++) { // ①
int j = 0;
while (j < m) {
if (text.charAt(i + j) != pattern.charAt(j)) { // ②
break;
}
j++; // ③
}
if (j == m) { // ④
return i;
}
}
return -1;
}
public static void main(String[] args) {
System.out.println(new Solution().strStr("AABABCDEF", "ABCD"));
}
}
①:此处条件是 i <= n - m
,这是因为如果主串剩余的长度如果没有模式串长,后面的字符也就不用进行比较了。
如上图,绿色位置到最后长度是3,而模式串的长度是4,明显没必要比较,一定不会匹配。
②:当遇到字符不匹配的时候,后续字符是无需比较的,直接结束本次循环。
③:如果字符相等,则继续比较后续字符。
④:j == m
说明整个模式串都匹配,这里我返回了匹配的开始索引位置。
上面的算法实现中,文本串i
每次匹配都保持不变(因为是通过text.charAt(i + j) != pattern.charAt(j)
进行比较的),只有一次匹配完成后,才会向后移动。
暴力算法还有另外一种实现方式,每次匹配下,每个字符进行比较的时候,指针i
也会随着一起向后移动,当发生不匹配的时候指针i
需要回溯,原理如下图所示:
道理上与之前的没啥区别,只是指针计算上有些差异而已,代码如下:
class Solution {
public int indexOf(String text, String pattern) {
// 煮串长度为n
int n = text.length();
// 模式串长度为m
int m = pattern.length();
// 煮串指针
int i = 0;
// 模式串指针
int j = 0;
while(i < n && j < m) {
// 获取主串的字符ci和模式串的字符cj
char ci = text.charAt(i);
char cj = pattern.charAt(j);
// 如果字符相等,则匹配后面的字符
if (ci == cj) { // ①
i++;
j++;
} else {
// 如果字符不相等,那么就需要回溯
i = i - j + 1; // ②
j = 0; // ③
}
if (j == m) { // ④
return i - m;
}
}
return -1;
}
public static void main(String[] args) {
System.out.println(new Solution().indexOf("AABABCDEF", "ABCD"));
}
}
①:对应位置字符相等,就继续比较后面的字符。
②:字符不相等,模式串指针回到头部,文本串的指针i应该回到上次比较的开始的位置的下一位置,为什么是i = i - j + 1;
呢,看如下图:
i = 3, j = 2
的时候失配了,考虑下i
从索引为1
的位置做了多少步到了3
的位置,就是走了j
步,那么i - j
后,那么i
就回到了本次比较的开始位置,下一次比较要从其后面开始所以再加上1
。
③:j == m
说明正好都匹配,(这里我开始想错了,我在想应该是 (j - 1) == m
,但实际上最后j还是会再加1,然后才不满足循环条件,不要犯错哦😄)。