模式匹配是数据结构中字符串的一种基本运算,给定一个子串,要求在某个字符串中找出与该子串相同的所有子串,这就是模式匹配。
文中代码是本人自己写的,实测有效,含JAVA和C++两种代码。干货充足吧。
蛮力算法(Brute-Force),简称BF算法。(男朋友算法,简单粗暴—_—!)
算法思想
BF算法的算法思想是:
从目标串T的的第一个字符起与模式串P的第一个字符比较。
若相等,则继续对字符进行后续的比较;否则目标串从第二个字符起与模式串的第一个字符重新比较。
直至模式串中的每个字符依次和目标串中的一个连续的字符序列相等为止,此时称为匹配成功,否则匹配失败。
通过下图示例,可一目了然:
算法性能
假设模式串的长度是m,目标串的长度是n。
最坏的情况是每遍比较都在最后出现不等,即没变最多比较m次,最多比较n-m+1遍。
总的比较次数最多为m(n-m+1),因此BF算法的时间复杂度为O(mn)。
BF算法中存在回溯,这影响到效率,因而在实际应用中很少采用。
代码
JAVA版本
2
3 static int bfMatch(String target, String pattern) {
4 int pos = -1;
5 int i = 0, j = 0, k = 0;
6
7 // 在没找到匹配pattern的子串前,遍历整个target
8 while (-1 == pos && i < target.length()) {
9
10 // 将目标串和模式串逐一比对,如果有不同的则退出
11 while (j < pattern.length() && target.charAt(i) == pattern.charAt(j)) {
12 i++;
13 j++;
14 }
15
16 if (j >= pattern.length()) { // 如果模式串扫描完,说明目标串中含有这个子串
17 pos = k;
18 } else { // 反之,没有扫描完,则从目标串的下一个字符开始重新逐一比对
19 j = 0;
20 k++;
21 i = k;
22 }
23 }
24
25 return pos;
26 }
27
28 public static void print(String target, String pattern, int index) {
29 if (-1 != index) {
30 System.out.format("[%s] is in the Pos = %d of [%s]\n", pattern, index, target);
31 } else {
32 System.out.format("[%s] is not in the [%s]\n", pattern, target);
33 }
34 }
35
36 public static void main(String[] args) {
37 String target = "Hello World";
38 String pattern = "llo";
39 String pattern2 = "Woe";
40
41 int index = bfMatch(target, pattern);
42 int index2 = bfMatch(target, pattern2);
43 print(target, pattern, index);
44 print(target, pattern2, index2);
45
46 }
47
48 }
C++版本
2 #include <string>
3
4 using namespace std;
5
6 int bfMatch(string target, string pattern) {
7 int pos = -1;
8 int i = 0, j = 0, k = 0;
9
10 // 在没找到匹配pattern的子串前,遍历整个target
11 while (-1 == pos && i < (int)target.length()) {
12
13 // 将目标串和模式串逐一比对,如果有不同的则退出
14 while (j < (int)pattern.length() && target[i] == pattern[j]) {
15 i++;
16 j++;
17 }
18
19 if (j >= (int)pattern.length()) { // 如果模式串扫描完,说明目标串中含有这个子串
20 pos = k;
21 } else { // 反之,没有扫描完,则从目标串的下一个字符开始重新逐一比对
22 j = 0;
23 k++;
24 i = k;
25 }
26 }
27
28 return pos;
29 }
30
31 void print(string target, string pattern, int index) {
32 if (-1 != index) {
33 cout << "[" << pattern << "] is in the Pos = " << index << " of [" << target << "]" << endl;
34 } else {
35 cout << "[" << pattern << "] is not in the [" << target << "]" << endl;
36 }
37 }
38
39 int main()
40 {
41 string target = "Hello World";
42 string pattern = "llo";
43 string pattern2 = "Woe";
44
45 int index = bfMatch(target, pattern);
46 int index2 = bfMatch(target, pattern2);
47 print(target, pattern, index);
48 print(target, pattern2, index2);
49 return 0;
50 }
运行结果
[Woe] is not in the [Hello World]
Knuth-Morris-Pratt算法(简称KMP),是由D.E.Knuth、J.H.Morris和V.R.Pratt共同提出的一个改进算法,消除了BF算法中回溯问题,完成串的模式匹配。
算法思想
显然,移回到前面已经比较过的位置,还是不能完全匹配。
KMP算法的思想是,设法利用这个已知信息,跳过前面已经比较过的位置,继续把它向后移,这样就提高了效率。
由此可知,KMP算法其实有两大要点:
(1) 计算跳转位置信息,这里我们称之为部分匹配表。
(2) 后移到指定位置,重新开始匹配。
首先,来看如何获得部分匹配表。
这个next 数组叫做部分匹配表。
对于next[]数组的定义如下:
对于BF算法中的例子,模式串P=“abcac”,根剧next[j]的定义,可得到下表:
j | 0 | 1 | 2 | 3 | 4 |
t[j] | a | b | c | a | c |
next[j] | -1 | 0 | 0 | 0 | 1 |
有了部分匹配表,就可以后移到指定位置
如果next[j] >= 0,则目标串的指针 i 不变,将模式串的指针 j 移动到 next[j] 的位置继续进行匹配;
若next[j] = -1,则将 i 右移1位,并将 j 置0,继续进行比较。
以上要点配合下面的示意图理解,效果会更好哦。
算法性能
在KMP算法中求next数组的时间复杂度为O(m),在后面的匹配中因目标串T的下标不用回溯,所以比较次数可记为n。
由此,得出KMP算法的总的时间复杂度为O(n+m)。
代码
JAVA版本
2
3 // 计算部分匹配表
4 public static int[] getNext(String pattern) {
5 int j = 0, k = -1;
6 int[] next = new int[pattern.length()];
7 next[0] = -1;
8 while (j < pattern.length() - 1) {
9 if (-1 == k || pattern.charAt(j) == pattern.charAt(k)) {
10 j++;
11 k++;
12 next[j] = k;
13 } else {
14 k = next[k];
15 }
16 }
17
18 return next;
19 }
20
21 // KMP算法
22 static int kmpMatch(String target, String pattern) {
23 int i = 0, j = 0, index = 0;
24 int[] next = getNext(pattern); // 计算部分匹配表
25
26 while (i < target.length() && j < pattern.length()) {
27 if (-1 == j || target.charAt(i) == pattern.charAt(j)) {
28 i++;
29 j++;
30 } else {
31 j = next[j]; // 如果出现部分不匹配,获取跳过的位置
32 }
33 }
34
35 if (j >= pattern.length())
36 index = i - pattern.length(); // 匹配成功,返回匹配子串的首字符下标
37 else
38 index = -1; // 匹配失败
39
40 return index;
41
42 }
43
44 // 打印完整序列
45 public static void printAll(int[] list) {
46 for (int value : list) {
47 System.out.print(value + "\t");
48 }
49 System.out.println();
50 }
51
52 public static void main(String[] args) {
53 String target = "ababcabcacbab";
54 String pattern = "abcac";
55 int index = kmpMatch(target, pattern);
56 System.out.format("[%s] is in the pos = %d of [%s]", pattern, index, target);
57 }
58
59 }
2 #include <string>
3
4 using namespace std;
5
6 const int MAX = 100;
7 int next[MAX] = {0};
8
9 // 计算部分匹配表
10 void getNext(string pattern) {
11 int j = 0, k = -1;
12 next[0] = -1;
13 while (j < (int)pattern.length() - 1) {
14 if (-1 == k || pattern[j] == pattern[k]) {
15 j++;
16 k++;
17 next[j] = k;
18 } else {
19 k = next[k];
20 }
21 }
22 return;
23 }
24
25 // KMP算法
26 int kmpMatch(string target, string pattern) {
27 int i = 0, j = 0, index = 0;
28 getNext(pattern); // 计算部分匹配表
29
30 while (i < (int)target.length() && j < (int)pattern.length()) {
31 if (-1 == j || target[i] == pattern[j]) {
32 i++;
33 j++;
34 } else {
35 j = next[j]; // 如果出现部分不匹配,获取跳过的位置
36 }
37 }
38
39 if (j >= (int)pattern.length())
40 index = i - pattern.length(); // 匹配成功,返回匹配子串的首字符下标
41 else
42 index = -1; // 匹配失败
43
44 return index;
45
46 }
47
48 void print(string target, string pattern, int index) {
49 if (-1 != index) {
50 cout << "[" << pattern << "] is in the Pos = " << index << " of [" << target << "]" << endl;
51 } else {
52 cout << "[" << pattern << "] is not in the [" << target << "]" << endl;
53 }
54 }
55
56 int main()
57 {
58 string target = "ababcabcacbab";
59 string pattern = "abcac";
60 int index = kmpMatch(target, pattern);
61 print(target, pattern, index);
62 return 0;
63 }
运行结果
http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
http://www.cnblogs.com/dolphin0520/archive/2011/08/24/2151846.html
欢迎阅读 程序员的内功——算法 系列