字符匹配算法是字符处理的一种基本操作,大致可分为【常规匹配算法】和【KMP算法】
【常规字符匹配算法】如下:
int index(char* s,char* t,int pos) { int i = pos; int j = 1; while( i<strlen(s) && j< strlen(t)) { if( s[i] == t[j] ) { ++i; ++j; } else { i = i-j+1; j = 0; } } if( j == strlen(t) ) return i-strlen(t); else return -1; }
这个算法在实际中经常使用,但有许多不必要的比较。算法复杂度为O(m*n),但实际工作中效率近似O(m+n),比较坏的情况较少出现。
【KMP算法】如下:
void get_next(char* t,int t_len,int *next) { int i = 0; int j = -1; next[0] = -1; while( i<t_len ) { if( j==-1 || t[i] == t[j] ) { ++i; ++j; if(i<t_len) { if(t[i] != t[j]) next[i] = j; else next[i] = next[j]; } } else j = next[j]; } } int index_KMP(char* s,char *t, int pos) { int s_len = strlen(s); int t_len = strlen(t); /*建立next数组记录跳步*/ int *next = new int[t_len]; get_next(t,t_len,next); /*开始匹配*/ int i = pos; int j = 0; while( i<s_len && j<t_len ) { if( j==-1 || s[i] == t[j] ) { ++i; ++j; } else { j = next[j];} } delete[] next; /*返回结果*/ if( j == t_len ) return i-t_len; else return -1; };
其中具体的匹配算法和常规算法十分类似,但是多了一个next数据来记录跳步,这样可以减少大量不必要的比较操作。上面的KMP算法采用的是一种改进的KMP算法,避免了KMP算法有时候做多余比较的一些情况(参见严蔚敏《数据结构》)。KMP算法将算法的时间复杂度降到了O(m+n),但也用到了O(n)的辅助空间,一般来说匹配的模式串都不会太长,所以一般辅助空间都是很小的,所以空间耗费是不需要多担心的。但是new操作建立next数据却是很费时间的,尤其是大量频繁调用KMP匹配算法的时候,这导致KMP可能比常规算法还慢,解决办法是判断模式串长度,一般情况下,都采用堆分配,最终算法如下:
void get_next(char* t,int t_len,int *next) { int i = 0; int j = -1; next[0] = -1; while( i<t_len ) { if( j==-1 || t[i] == t[j] ) { ++i; ++j; if(i<t_len) { if(t[i] != t[j]) next[i] = j; else next[i] = next[j]; } } else j = next[j]; } }; int index_KMP_large(char* s,int s_len,char *t,int t_len, int pos) { /*建立next数组记录跳步*/ int *next = new int[t_len]; get_next(t,t_len,next); /*开始匹配*/ int i = pos; int j = 0; while( i<s_len && j<t_len ) { if( j==-1 || s[i] == t[j] ) { ++i; ++j; } else { j = next[j];} } delete[] next; /*返回结果*/ if( j == t_len ) return i-t_len; else return -1; }; int index_KMP_fast(char* s,int s_len,char *t,int t_len, int pos) { /*建立next数组记录跳步*/ int next[256]; get_next(t,t_len,next); /*开始匹配*/ int i = pos; int j = 0; while( i<s_len && j<t_len ) { if( j==-1 || s[i] == t[j] ) { ++i; ++j; } else { j = next[j];} } /*返回结果*/ if( j == t_len ) return i-t_len; else return -1; }; int index_tmp(char *s, char *t, int pos) { int s_len = strlen(s); int t_len = strlen(t); int index = -1; if(t_len > s_len) return index; if(t_len < 256 ) index = index_KMP_fast(s,s_len,t,t_len,pos); else index = index_KMP_large(s,s_len,t,t_len,pos); return index; };
上述的写法,使得整个算法看山去,特别长而繁琐。你可以把代码写得更加简洁:
void get_next(char* t,int t_len,int *next) { int i = 0; int j = -1; next[0] = -1; while( i<t_len ) { if( j==-1 || t[i] == t[j] ) { ++i; ++j; if(i<t_len) { if(t[i] != t[j]) next[i] = j; else next[i] = next[j]; } } else j = next[j]; } } int index_op(char *s, char *t, int pos) { int s_len = strlen(s); int t_len = strlen(t); int flag = 0; if( t_len > s_len ) return -1; int *next = NULL; if( t_len <256 ) { int tmp[256]; next = tmp; } else { next = new int[t_len]; flag = 1; } get_next(t,t_len,next); /*开始匹配*/ int i = pos; int j = 0; while( i<s_len && j<t_len ) { if( j==-1 || s[i] == t[j] ) { ++i; ++j; } else { j = next[j];} } if( flag==1 ) delete []next; /*返回结果*/ if( j == t_len ) return i-t_len; else return -1; };
另外,最后提一句,要理解KMP算法,按照一般课本的方式显得特别繁琐,容易绕晕,其实KMP算法的本质就是模拟DFA(有限状态自动机),从自动机的角度理解KMP其实才是最简洁直观的。