学习KMP算法

KMP算法的核心是提前根据模式串计算出来一张表,这张表中记录着什么情况下能够往后移的幅度大一些,以减少重复的匹配,理解步骤如下:

首先,应知道什么是失配,就是模式串在和目标文本的遍历匹配过程中遇到了不匹配的字符。

其次,失配无非两种情况:首字符失配,非首字符失配。

首字符失配时,模式串后移一步,匹配后面的字符。

非首字符失配时,这时有个有价值的信息,就是我们已经知道了失配的字符之前的那些没有失配的那些字符,那些字符有什么特点呢?怎么样才能帮助我们减少匹配次数呢?

那些字符的特点是:是模式串的从头开始的一个子串!

KMP算法的核心正是分析它的子串,从而减少匹配次数。

这个特点看起来很简单,合理利用的话,可以大幅减少匹配次数。

我们的目的是,对于这些匹配过的字符,即这个模式串的“子串”,我们尽量不想再去遍历他们了,这时我们可以分析这个子串,它无非这两种情况:

情况一:子串的头不能覆盖到尾部,即子串的尾部是否有子串的子串,例如:

abcde,对于它,我们可以放心的把模式串后移到e后面,直接跳过了子串长度个字符,因为这个子串从尾部往前覆盖无论如何也覆盖不上,要想首字符不失配,必须保证这个子串的从尾部开始的子串是它从头开始的子串,就像下面这种情况。

情况二:子串的头可以覆盖到尾部:即子串从尾部开始,存在“小子串”是这个子串从头部开始的“小子串”,例如:

abcdeab,对于他,尾部的ab正好是abcdeab的一个从头开始的“小子串”,这样才能满足非首字符失配的条件,这种情况下,将会根据“小子串”的长度去移动模式串,如果不满足这个条件,就是首字符失配,那样就直接把模式串的首字符放到指针位置去匹配就可以了,就像第一种情况那样。

对于情况一和情况二,我们需要提前就计算出来模式串的那些子串头能覆盖到尾,即存在子串的“小子串”,如abcdeabfed是模式串的话,abcdeab就是一个子串,ab就是这个子串的“小子串”。计算出来的数据放到一张表里,根据这张表,我们就能跳过一些没必要的遍历,完成快速匹配了,

最后,上代码:

  1 #include <stdlib.h>
  2 #include <string>
  3 #include <sys/timeb.h>
  4 #include <fstream>
  5 /**
  6 /* @author:zanzan101
  7 */
  8 
  9 using namespace std;
 10 
 11 // 计算每一个子串的K
 12 int aux(const char* str, int len)
 13 {
 14     if(len <= 1)
 15         return 0;
 16     bool b;
 17     for(int i = 1; i < len; i++)
 18     {
 19         b = true;
 20         for(int j = 0; j < len-i && b; j++)
 21             if(str[j] != str[i+j])
 22                 b = false;
 23         if(b)
 24             return len - i;                // 返回K,K=1表示能覆盖最后一个字符
 25     }
 26     return 0;                            // 返回0表示无法覆盖
 27 }
 28 
 29 // 计算所有子串的K
 30 int* cal_table(const char* pattern_str, int len)
 31 {
 32     int* table = new int[len];
 33     table[0] = -1;
 34     table[1] = 0;
 35     for(int i = 2; i < len; i++)        // 失配时,子串长度一定小于len,所以,不需要计算table[len]
 36         table[i] = aux(pattern_str, i);
 37     return table;
 38 }
 39 
 40 // 查找匹配位置,返回下标
 41 int find_string(const char* str, int len_str, const char* pattern_str, int len_pattern_str)
 42 {
 43     if(len_str < 1)
 44         return -1;
 45     int* table = cal_table(pattern_str, len_pattern_str);
 46     bool b = true;
 47     int i = 0, j = 0;
 48     while(i <= len_str - len_pattern_str)
 49     {
 50         j = 0;
 51         b = true;
 52         while(j < len_pattern_str && b)
 53         {
 54             if(str[i+j] != pattern_str[j])
 55             {
 56                 b = false;
 57                 i += j - table[j];    // 当在长度为j+1的子串处失配时,查找长度为j的子串的最大k,即table[j]
 58             }else
 59                 j++;
 60         }
 61         if(b)
 62         {
 63             delete table;
 64             return i;
 65         }
 66     }
 67     delete table;
 68     return -1;
 69 }
 70 // 查找匹配位置,返回下标
 71 int find_string_opt(const char* str, int len_str, const char* pattern_str, int len_pattern_str)
 72 {
 73     if(len_str < 1)
 74         return -1;
 75     int* table = cal_table(pattern_str, len_pattern_str);
 76     int i = 0, j = 0;
 77     while(i <= len_str - len_pattern_str)
 78     {
 79         while(j < len_pattern_str)
 80         {
 81             if(str[i+j] != pattern_str[j])
 82             {
 83                 i += j - table[j];            // 当在长度为j+1的子串处失配时,查找长度为j的子串的最大k,即table[j]
 84                 j = table[j]>0?table[j]:0;    // 这句代码如果改成 j = max(table[j], 0); 算法将会变慢
 85             }else
 86                 j++;
 87         }
 88 
 89         delete table;
 90         return i;
 91 
 92     }
 93     delete table;
 94     return -1;
 95 }
 96 
 97 
 98 // 查找匹配位置,显示匹配过程
 99 int find_string_for_fun(const char* str, int len_str, const char* pattern_str, int len_pattern_str)
100 {
101     if(len_str < 1)
102         return -1;
103     int* table = cal_table(pattern_str, len_pattern_str);
104     bool b = true;
105     for(int i = 0; i <= len_str - len_pattern_str;)
106     {
107         b = true;
108         for(int j = 0; j < len_pattern_str && b; j++)
109         {
110             if(str[i+j] != pattern_str[j])
111             {
112                 for(int k = 0; k < i; k++)
113                     printf(" ");
114                 for(int m = 0; m < len_pattern_str; m++)
115                     if(m != j)
116                         printf("%c", pattern_str[m]);
117                     else
118                         printf("_");
119                 printf("\n");
120                 b = false;
121                 i += j - table[j];    // 当在长度为j+1的子串处失配时,查找长度为j的子串的最大k,即table[j]
122             }
123         }
124         if(b)
125         {
126             for(int k = 0; k < i; k++)
127                 printf(" ");
128             for(int m = 0; m < len_pattern_str; m++)
129                 printf("%c", pattern_str[m]);
130             printf("\n");
131             delete table;
132             return i;
133         }
134     }
135     delete table;
136     return -1;
137 }
138 
139 // 查找匹配位置,显示匹配过程
140 int find_string_for_fun_opt(const char* str, int len_str, const char* pattern_str, int len_pattern_str)
141 {
142     if(len_str < 1)
143         return -1;
144     int* table = cal_table(pattern_str, len_pattern_str);
145 
146     int i = 0, j = 0;
147     while(i <= len_str - len_pattern_str)
148     {
149         while(j < len_pattern_str)
150         {
151             if(str[i+j] != pattern_str[j])
152             {
153                 for(int k = 0; k < i; k++)
154                     printf(" ");
155                 for(int m = 0; m < len_pattern_str; m++)
156                     if(m != j)
157                         printf("%c", pattern_str[m]);
158                     else
159                         printf("_");
160                 printf("\n");
161                 i += j - table[j];        // 当在长度为j+1的子串处失配时,查找长度为j的子串的最大k,即table[j]
162                 j = table[j]>0?table[j]:0;    // 这里做了一点点优化,比上面的函数可以少匹配一些
163             }else
164                 j++;
165         }
166 
167         for(int k = 0; k < i; k++)
168             printf(" ");
169         for(int m = 0; m < len_pattern_str; m++)
170             printf("%c", pattern_str[m]);
171         printf("\n");
172         delete table;
173         return i;
174 
175     }
176     delete table;
177     return -1;
178 }
179 
180 int main()
181 {
182     timeb time_begin;
183     timeb time_end;
184     unsigned int seconds;
185     unsigned int miseconds;
186     //////////////////////////////////////////////////////////////////////////
187     // 短文本匹配
188     const char* long_str = "adadaaddabadaadadaadadaadadaadaaddad";
189     const char* short_str = "adaaddad";
190     printf("%s\n", long_str);
191     printf("%s\n", short_str);
192     ftime(&time_begin);    
193     int idx_fun = find_string_for_fun(long_str, strlen(long_str), short_str, strlen(short_str));
194     ftime(&time_end);
195     printf("_ 表示失配的位置\n\n");
196     seconds = time_end.time - time_begin.time;
197     miseconds = time_end.millitm - time_begin.millitm;
198     miseconds = seconds * 1000 + miseconds;
199     printf("KMP方法:\t处理时间为:\t%u\t毫秒\n", miseconds);
200 
201     printf("%s\n", long_str);
202     printf("%s\n", short_str);
203     ftime(&time_begin);    
204     idx_fun = find_string_for_fun_opt(long_str, strlen(long_str), short_str, strlen(short_str));
205     ftime(&time_end);
206     printf("_ 表示失配的位置\n");
207     seconds = time_end.time - time_begin.time;
208     miseconds = time_end.millitm - time_begin.millitm;
209     miseconds = seconds * 1000 + miseconds;
210     printf("KMP_2方法:\t处理时间为:\t%u\t毫秒\n", miseconds);
211     printf(">> result is :\n%s\n\n", long_str+idx_fun);
212 
213 
214     //////////////////////////////////////////////////////////////////////////
215     // 处理一大批数据,和普通方法对比
216     char* buff = new char[10240000]; // 10000KB的缓冲区
217     ifstream input_file;
218     int idx = -1;
219     input_file.open("D:\\input.txt");
220     const char* str = "Uncle Vernon was waiting beyond the barrier. Mrs. Weasley wasclose by him. She hugged Harry very tightly when she saw him andwhispered in his ear, \"I think Dumbledore will let you come to uslater in the summer. Keep in touch, Harry.\"";
221     bool b;
222 
223 
224     //////////////////////////////////////////////////////////////////////////
225     // KMP方法
226     ftime(&time_begin);    
227     do{
228         b = input_file.read(buff, 10240000);
229         idx = find_string(buff, input_file.gcount(), str, strlen(str));
230         if(idx >= 0)
231             break;
232     }while(b);
233     ftime(&time_end);
234     seconds = time_end.time - time_begin.time;
235     miseconds = time_end.millitm - time_begin.millitm;
236     miseconds = seconds * 1000 + miseconds;
237     printf("KMP方法:\t处理时间为:\t%u\t毫秒\n", miseconds);
238 
239     input_file.close();
240 
241     input_file.open("D:\\input.txt");
242     //////////////////////////////////////////////////////////////////////////
243     // KMP_2方法
244     ftime(&time_begin);    
245     do{
246         b = input_file.read(buff, 10240000);
247         idx = find_string_opt(buff, input_file.gcount(), str, strlen(str));
248         if(idx >= 0)
249             break;
250     }while(b);
251     ftime(&time_end);
252     seconds = time_end.time - time_begin.time;
253     miseconds = time_end.millitm - time_begin.millitm;
254     miseconds = seconds * 1000 + miseconds;
255     printf("KMP_2方法:\t处理时间为:\t%u\t毫秒\n", miseconds);
256 
257     input_file.close();
258     input_file.open("D:\\input.txt");
259     bool f = false;
260     int len = strlen(str);
261 
262     //////////////////////////////////////////////////////////////////////////
263     // 朴素的方法
264     ftime(&time_begin);    
265     do{
266         b = input_file.read(buff, 10240000);
267         f = true;
268         for(int i = 0; i <= input_file.gcount()-len; i++)
269         {
270             for(int j = 0; j < len; j++)
271                 if(buff[i+j] != str[j])
272                     f = false;
273             if(f)
274             {
275                 idx = i;
276                 break;
277             }
278         }
279     }while(b&!f);
280     ftime(&time_end);
281     seconds = time_end.time - time_begin.time;
282     miseconds = time_end.millitm - time_begin.millitm;
283     miseconds = seconds * 1000 + miseconds;
284     printf("朴素的方法:\t处理时间为:\t%u\t毫秒\n", miseconds);
285     
286     input_file.close();
287     if(idx >= 0)
288     {
289         char* res = new char[strlen(str)];
290         memcpy(res, buff+idx, strlen(str));
291         res[strlen(str)] = 0;
292         printf("\n>> result is : \n%s\n", res);
293     }
294 
295     delete[] buff;
296 
297     system("pause");
298     return 0;
299 }

输出结果:

adadaaddabadaadadaadadaadadaadaaddad
adaaddad
ada_ddad
  adaadda_
        a_aaddad
         _daaddad
          adaad_ad
             ada_ddad
               adaad_ad
                  ada_ddad
                    adaad_ad
                       ada_ddad
                         adaad_ad
                            adaaddad
_ 表示失配的位置

KMP方法:       处理时间为:    15      毫秒
adadaaddabadaadadaadadaadadaadaaddad
adaaddad
ada_ddad
  adaadda_
        a_aaddad
         _daaddad
          adaad_ad
             ada_ddad
               adaad_ad
                  ada_ddad
                    adaad_ad
                       ada_ddad
                         adaad_ad
                            adaaddad
_ 表示失配的位置
KMP_2方法:     处理时间为:    16      毫秒
>> result is :
adaaddad

KMP方法:       处理时间为:    46      毫秒
KMP_2方法:     处理时间为:    47      毫秒
朴素的方法:    处理时间为:    3438    毫秒

>> result is :
Uncle Vernon was waiting beyond the barrier. Mrs. Weasley wasclose by him. She h
ugged Harry very tightly when she saw him andwhispered in his ear, "I think Dumb
ledore will let you come to uslater in the summer. Keep in touch, Harry."
请按任意键继续. . .

 

 

转载于:https://www.cnblogs.com/zanzan101/p/3346591.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值