1.单模式匹配
就是在一些文本中查找某一个子字符串的算法,效率较高的有以下几种。
KMP算法:全称Knuth-Morris-Pratt算法 预处理时间Θ(m) 匹配搜索时间 Θ(n)
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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)
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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的。
![ContractedBlock.gif](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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 ;
}