字符串搜索算法
1.单模式匹配
就是在一些文本中查找某一个子字符串的算法,效率较高的有以下几种。
KMP算法:全称Knuth-Morris-Pratt算法 预处理时间Θ(m) 匹配搜索时间 Θ(n)
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 const int * get_prefix(const char * P)
6 {
7 int * pi = (int *)malloc(sizeof(int) * strlen(P));
8 pi[0] = -1;
9 int i = 1;
10 int j = -1;
11 while (P[i])
12 {
13 while (j >= 0 && P[j + 1] != P[i])
14 {
15 j = pi[j];
16 }
17 if (P[j + 1] == P[i])
18 {
19 ++j;
20 }
21 pi[i] = j;
22 ++i;
23 }
24 return pi;
25 }
26
27 void kmp_match(const char * T, const char * P)
28 {
29 const int * pi = get_prefix(P);
30 int i = 0;
31 int j = -1;
32 while (T[i])
33 {
34 while (j >= 0 && P[j + 1] != T[i])
35 {
36 j = pi[j];
37 }
38 if (P[j + 1] == T[i])
39 {
40 ++j;
41 }
42 if (0 == P[j + 1])
43 {
44 printf("%s\n", T + i - j);
45 j = pi[j];
46 }
47 ++i;
48 }
49 free(pi);
50 }
51
52 int main(int argc, char * argv[])
53 {
54 kmp_match("abcdabcdabcdabcd", "abc");
55
56 return 0;
57 }
58
59
60 参考:《算法导论》
61
62 ---------------------------------------------------------------------------
63
64 /*
65 * Knuth-Morris-Pratt 字符串匹配算法的三种实现。
66 * 匹配部分都一样,差异只在求 next 数组。:)
67 */
68
69 #include <stdio.h>
70 #include <stdlib.h>
71 #include <string.h>
72
73 /*
74 * 实现一
75 */
76 char * kmp1(char * content, char * pattern)
77 {
78 int i;
79 int j;
80 int len;
81 int * next;
82
83 if (NULL == content || NULL == pattern)
84 {
85 return NULL;
86 }
87
88 len = strlen(pattern);
89 next = (int *)malloc(len * sizeof(int));
90
91 /* Get the "next" array. */
92 next[0] = -1;
93 for (i = 1; pattern[i] != 0; ++i)
94 {
95 j = next[i - 1];
96 while (pattern[i - 1] != pattern[j] && j >= 0)
97 {
98 j = next[j];
99 }
100 next[i] = j + 1;
101 }
102
103 /* Match. */
104 i = 0;
105 j = 0;
106 while (content[i] && pattern[j])
107 {
108 if (content[i] == pattern[j])
109 {
110 ++i;
111 ++j;
112 }
113 else
114 {
115 j = next[j];
116 if (-1 == j)
117 {
118 ++i;
119 ++j;
120 }
121 }
122 }
123
124 free(next);
125
126 if (pattern[j])
127 {
128 return NULL;
129 }
130 else
131 {
132 return &content[i - j];
133 }
134 }
135
136 /*
137 * 实现二
138 */
139 char * kmp2(char * content, char * pattern)
140 {
141 int i;
142 int j;
143 int len;
144 int * next;
145
146 if (NULL == content || NULL == pattern)
147 {
148 return NULL;
149 }
150
151 len = strlen(pattern);
152 next = (int *)malloc(len * sizeof(int));
153
154 /* Get the "next" array. */
155 next[0] = -1;
156 i = 0;
157 j = -1;
158 while (pattern[i])
159 {
160 if (-1 == j || pattern[i] == pattern[j])
161 {
162 ++i;
163 ++j;
164 next[i] = j;
165 }
166 else
167 {
168 j = next[j];
169 }
170 }
171
172 /* Match. */
173 i = 0;
174 j = 0;
175 while (content[i] && pattern[j])
176 {
177 if (content[i] == pattern[j])
178 {
179 ++i;
180 ++j;
181 }
182 else
183 {
184 j = next[j];
185 if (-1 == j)
186 {
187 ++i;
188 ++j;
189 }
190 }
191 }
192
193 free(next);
194
195 if (pattern[j])
196 {
197 return NULL;
198 }
199 else
200 {
201 return &content[i - j];
202 }
203 }
204
205 /*
206 * 实现三
207 *
208 * 实现二的改进,改进处见注释。
209 */
210 char * kmp3(char * content, char * pattern)
211 {
212 int i;
213 int j;
214 int len;
215 int * next;
216
217 if (NULL == content || NULL == pattern)
218 {
219 return NULL;
220 }
221
222 len = strlen(pattern);
223 next = (int *)malloc(len * sizeof(int));
224
225 /* Get the "next" array. */
226 next[0] = -1;
227 i = 0;
228 j = -1;
229 while (pattern[i])
230 {
231 if (-1 == j || pattern[i] == pattern[j])
232 {
233 ++i;
234 ++j;
235
236 /* 此处是对实现二的改进。 */
237 if (pattern[i] == pattern[j])
238 {
239 next[i] = next[j];
240 }
241 else
242 {
243 next[i] = j;
244 }
245 }
246 else
247 {
248 j = next[j];
249 }
250 }
251
252 /* Match. */
253 i = 0;
254 j = 0;
255 while (content[i] && pattern[j])
256 {
257 if (content[i] == pattern[j])
258 {
259 ++i;
260 ++j;
261 }
262 else
263 {
264 j = next[j];
265 if (-1 == j)
266 {
267 ++i;
268 ++j;
269 }
270 }
271 }
272
273 free(next);
274
275 if (pattern[j])
276 {
277 return NULL;
278 }
279 else
280 {
281 return &content[i - j];
282 }
283 }
284
285 int main(int argc, char * argv[])
286 {
287 printf("%s\n", kmp1(argv[1], argv[2]));
288 printf("%s\n", kmp2(argv[1], argv[2]));
289 printf("%s\n", kmp3(argv[1], argv[2]));
290
291 return 0;
292 }
BM算法:全称Boyer-Moore string search algorithm 预处理时间Θ(m + |Σ|) 匹配搜索时间Ω(n/m), O(n)
1 using System;
2 namespace stringsearch
3 {
4 /// <summary>
5 /// 字符串搜索的基本抽象类
6 /// </summary>
7 public abstract class StringSearchTool
8 {
9 public enum Search
10 {
11 NOT_FOUND,
12 SEARCH_EXACT,
13 SEARCH_CASELESS
14 }
15
16 protected Search search;
17 protected String pattern;
18
19 public string Pattern
20 {
21 get
22 {
23 return pattern;
24 }
25 set
26 {
27 //大小写暂时无用处
28 if (search == Search.SEARCH_CASELESS)
29 {
30 pattern = value;
31 pattern.ToUpper();
32 }
33 else
34 {
35 pattern = value;
36
37 }
38 }
39 }
40
41 public StringSearchTool()
42 {
43 search = Search.SEARCH_CASELESS;
44 pattern = null;
45 }
46 public StringSearchTool(string p)
47 {
48 search = Search.SEARCH_CASELESS;
49 pattern = p;
50
51 }
52
53
54 public StringSearchTool(string p, Search type)
55 {
56 search = type;
57 pattern = p;
58 }
59 public int getPatternLength()
60 {
61 return pattern.Length;
62 }
63 public Search getSearchType()
64 {
65 return search;
66 }
67 public int find(string target)
68 {
69 return find(target, 0);
70 }
71 public abstract int find(string target, int start);
72 }
73 }
74 // BoyerMoore算法
75 using System;
76 namespace stringsearch
77 {
78 /// <summary>
79 ///
80 /// </summary>
81 public class BoyerMoore : stringsearch.StringSearchTool
82 {
83 protected int[] delta;
84 private static readonly int DELTA_SIZE = 65536;
85 public BoyerMoore()
86 : base()
87 {
88
89 }
90 public BoyerMoore(string p)
91 : base(p)
92 {
93
94 }
95 public BoyerMoore(string p, Search type)
96 : base(p, type)
97 {
98
99 }
100
101 public override int find(string target, int start)
102 {
103 if ((pattern == null) || (start < 0))
104 return (int)Search.NOT_FOUND;
105 String target2;
106 //if(search==Search.SEARCH_CASELESS)
107 // target2=target.ToUpper();
108 //else
109 target2 = target;
110 int t = start + pattern.Length;
111 while (t <= target2.Length)
112 {
113 int p = pattern.Length;
114 while (pattern[p - 1] == target2[t - 1])
115 {
116 if (p > 1)
117 {
118 --p;
119 --t;
120 }
121 else
122 {
123 return t - 1;
124 }
125 }
126 t += delta[(int)target2[t - 1]];
127 }
128 return (int)Search.NOT_FOUND;
129 }
130 public new string Pattern
131 {
132 get
133 {
134 return base.Pattern;
135 }
136 set
137 {
138 base.Pattern = value;
139 int n;
140 delta = new int[DELTA_SIZE];
141 for (n = 0; n < DELTA_SIZE; ++n)
142 delta[n] = pattern.Length;
143 for (n = 1; n < pattern.Length; ++n)
144 delta[(int)pattern[n - 1]] = pattern.Length - n;
145 delta[(int)pattern[pattern.Length - 1]] = 1;
146 }
147 }
148 }
149 }
150 // 测试代码(部分):
151 private void button1_Click(object sender, System.EventArgs e)
152 {
153 String terget = label1.Text;
154 String pattern=textBox1.Text;
155 BoyerMoore bm=new BoyerMoore();
156 bm.Pattern=pattern;
157 if (bm.find(terget,0)>0)
158 MessageBox.Show(this,"你是不是在找 "+pattern+" ?"+"恭喜你找到了!");
159 else
160 MessageBox.Show(this,"下面的这段话中没有找到你所查找的文字!");
161 }
2. 有限模式集合匹配
就是在字符串中查找多个子字符串的算法,常用于查找字典中的单词和一些脏字匹配算法
Aho-Corasick算法:这是一种字典匹配算法,它用于在输入文本中查找字典中的字符串。时间复杂度是线性的。
基本原理:该算法利用类似后缀树的方法构造一个trie结构,匹配时利用该结构来搜索。
Aho-Corasick:可以认为是KMP算法在多串查找的扩展。先把所有要查找的串放在一起建一棵trie树。然后从源串的各个位置开始的串到trie树里查找。这样每次查找的复杂度为O(m),m为最长的子串。总共要查n次,所以复杂度为O(nm)。这只能说是brute force算法在多串下的扩展。所以还要在trie树里添加一些转移,使得源串在匹配过程中不出现回退。假设当前节点为i,源串匹配到字符c,如果节点i不存在c字母对应的转移。这时候应该跳转到一个节点j,从trie树根节点到这个节点的字母组成的字符串,应该是从根节点组成的字符串的后缀,如果有多个这样的节点,则跳转到最短的一个。分析一下这个自动机在匹配时的复杂度。要匹配过程中,源串不回退,所以遍历源串的复杂度为O(n)。但是在匹配失败的时候,会出现跳转,可能要跳转很多次才可以匹配成功。但是注意一点,每次跳转使得深度至少减1,而深度至多跟源串匹配的长度相等,所以可以跳转的次数不会超过源串的长度,所以总的复杂度是O(n)。
要注意一个地方,后缀关系是可以传递的,a是b的后缀,b是c的后缀,则a是c的后缀。所以上面的表达式,最终是把一个串的后有后缀,从长到短串成了一个链表。
难的地方就是自动机的构造。为了便于分析,把建trie树跟增加跳转分成两个过程。假设现在已经建好trie树。要为一个节点增加跳转,最简单的方法是,把根结点到它一路上的字符串组成的字符串,求后缀,再到trie树里查找,因为是求最长的后缀,所以从长到短,逐一去查找,直至找到为止,找到的节点就是要跳转的节点。这种方法的复杂度很高。观察一下,假设在trie树里,i节点是j节点的父结点,j的跳转结点的父结点表示的串,也是i的后缀。反过来说,根据一个节点的父结点的跳转可以快速地找到这个结点的跳转。假设当前节点的父结点的跳转结点为k,下一个要匹配的字符为c,看k是否有c的跳转,如果没有,则k继续跳转。会否出现没有找到合法的跳转,而k又不能再跳转的情况呢?初始的时候,把所有节点的跳转都指向根结点,可以避免这种情况。
在实现的时候,因为跳转总是从深度大的结点跳到深度小的结点,所以对trie树作广度遍历。现在来分析一下构造的复杂度。trie树中结点的总数不会超过待查找的子串的长度和。但是每个结点的查找会进行多次跳转。分析一个子串它对应的一系列节点,每交跳转会使得深度减1,而这些节点的深点至多为子典的长度,所以一个子串对应的所有结点的构造费用之和,不会超过字串的长度,所以总的构造复杂度是跟字符长度和成正比的。
Commentz-Walter 算法:是BM算法的自然扩展,它的速度并不快
Rabin-Karp string search算法:该算法最差复杂度不好,因此运用的并不广泛。
1. 问题描述
给定目标字符串 T[0..n-1] (基于 0 的数组,数组长度为 n ),和模式串 P[0..m-1] ,问 P 可否匹配 T 中的任意子串,如果可以,返回匹配位置。
2. 问题分析
直观分析
brute-force 的蛮力法,适用于较小规模的字符串匹配。
优化
主要介绍 3 种优化办法,分别具体为: Rabin-Karp 算法,有限自动机和 KMP 算法。将分为 3 篇博文分别讨论。本小节主要介绍 Rabin-Karp 算法。
得出算法
Rabin-Karp 算法(以下简称为 RK 算法),是基于这样的思路:即把串看作是字符集长度进制的数,由数的比较得出字符串的比较结果。例如,给定字符集为∑ ={0,1,2,3,4,5,6,7,8,9} ,∑长度为 d=10 ,那么任何以∑为字符集的串都可看作 d (此处为 10 )进制的数。
记模式串 P[0..n-1] 对应的数值为 P , T[0..n-1] 所有长度为 m 的子串对应的数值为 ts ,设 P 和 T 都是基于字符集长度为 | ∑ |=d 的字符串。
那么, ts 即为 T[s..s+m] 对应的数值,这里 0<=s<=n-m-1 。
P = P[m]+d*(P[m-1]+d*(P[m-2]+..)))
同样 t0 也可类似求得。
最重要的是如何从 ts 求出 ts+1 。
ts+1 =T[s+m]+d*(ts +dm-1 *T[s])
注:此处是该算法的关键,即在常数时间内能够计算出下一个 m 长度的字串对应的数值。初看比较抽象,举个例子就比较明白了,设 x=12345 ,现在是已知长度为 3 的数值 234 ,现在要求 345 对应的数值,可以这样来得到: 345 = 5 + 10*(234-102 *2)
3. 算法描述
求出所有 m 长度子串所对应的数值,对数值进行比较,继而得出子串是否匹配。当模式串长度很大时,这时对应的数值会很大,比较起来比较麻烦,可使用对一个大奇数取模后进行比较。
4. 具体实现
这里实现的只是m值较小时的情形,大整数需要特定的类的支持(如可自定义大整数类),选取10进制的数是为了方便起见,当然字母也是OK的。
#include "iostream"
#include "string"
#include "cmath"
using namespace std;
// get the value of the character in the set
int getV(char p, string set)
{
for(int i=0; i<set.length(); i++)
{
if (p==set[i])
return i;
}
return -1;
}
// d is the size of the character set
int RK(string T, string P,string set)
{
int d = int(set.length());
int n = T.length();
int m = P.length();
int h = pow(double(d), m-1);
int p=0;
int t = 0;
for(int i=0; i<m; i++)
{
p = d*p + getV(P[i],set);
t = d*t + getV(T[i], set);
}
for (int s=0; s<=n-m; s++)
{
cout<<"p,t is "<<p<<","<<t<<endl;
if (p==t)
return s;
if (s<n-m)
t = getV(T[s+m],set)+d*(t-h*getV(T[s],set));
}
return -1;
}
int main()
{
// set is the character set
string set= "0123456789";
// pattern P
string P = "2365";
// T is the string to match
string T = "258569236589780";
int i = RK(T, P, set);
cout<<"the postition is:"<<i<<endl;
return 0;
}