KMP算法的核心是提前根据模式串计算出来一张表,这张表中记录着什么情况下能够往后移的幅度大一些,以减少重复的匹配,理解步骤如下:
首先,应知道什么是失配,就是模式串在和目标文本的遍历匹配过程中遇到了不匹配的字符。
其次,失配无非两种情况:首字符失配,非首字符失配。
首字符失配时,模式串后移一步,匹配后面的字符。
非首字符失配时,这时有个有价值的信息,就是我们已经知道了失配的字符之前的那些没有失配的那些字符,那些字符有什么特点呢?怎么样才能帮助我们减少匹配次数呢?
那些字符的特点是:是模式串的从头开始的一个子串!
KMP算法的核心正是分析它的子串,从而减少匹配次数。
这个特点看起来很简单,合理利用的话,可以大幅减少匹配次数。
我们的目的是,对于这些匹配过的字符,即这个模式串的“子串”,我们尽量不想再去遍历他们了,这时我们可以分析这个子串,它无非这两种情况:
情况一:子串的头不能覆盖到尾部,即子串的尾部是否有子串的子串,例如:
abcde,对于它,我们可以放心的把模式串后移到e后面,直接跳过了子串长度个字符,因为这个子串从尾部往前覆盖无论如何也覆盖不上,要想首字符不失配,必须保证这个子串的从尾部开始的子串是它从头开始的子串,就像下面这种情况。
情况二:子串的头可以覆盖到尾部:即子串从尾部开始,存在“小子串”是这个子串从头部开始的“小子串”,例如:
abcdeab,对于他,尾部的ab正好是abcdeab的一个从头开始的“小子串”,这样才能满足非首字符失配的条件,这种情况下,将会根据“小子串”的长度去移动模式串,如果不满足这个条件,就是首字符失配,那样就直接把模式串的首字符放到指针位置去匹配就可以了,就像第一种情况那样。
对于情况一和情况二,我们需要提前就计算出来模式串的那些子串头能覆盖到尾,即存在子串的“小子串”,如abcdeabfed是模式串的话,abcdeab就是一个子串,ab就是这个子串的“小子串”。计算出来的数据放到一张表里,根据这张表,我们就能跳过一些没必要的遍历,完成快速匹配了,
最后,上代码:
1 #include2 #include 3 #include 4 #include 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 else118 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 else159 printf("_");160 printf("\n");161 i += j - table[j]; // 当在长度为j+1的子串处失配时,查找长度为j的子串的最大k,即table[j]162 j = table[j]>0?table[j]:0; // 这里做了一点点优化,比上面的函数可以少匹配一些163 }else164 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 }
输出结果:
adadaaddabadaadadaadadaadadaadaaddadadaaddadada_ddad adaadda_ a_aaddad _daaddad adaad_ad ada_ddad adaad_ad ada_ddad adaad_ad ada_ddad adaad_ad adaaddad_ 表示失配的位置KMP方法: 处理时间为: 15 毫秒adadaaddabadaadadaadadaadadaadaaddadadaaddadada_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 :adaaddadKMP方法: 处理时间为: 46 毫秒KMP_2方法: 处理时间为: 47 毫秒朴素的方法: 处理时间为: 3438 毫秒>> result is :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."请按任意键继续. . .