子串的定位操作通常称做串的模式匹配,是各种串处理系统中最重要的操作之一.在很多应用中都会涉及子串的定位问题,如普通的字符串查找问题.如果我们把模式匹配的串看成一字节流的话,那应用空间一下子就广阔了很多,如HTTP协议里就是字节流,有各种关键的字节流字段,对HTTP数据进行解释就需要用到模式匹配算法.
模式匹配算法里两个最为重要的算法:KMP与BM算法
图2-1 KMP算法的一个例子
如果是普通的匹配算法,那么接下来,模式串的下一个匹配将如上一节读者所看到的那样,回溯到第二个位置b处。而KMP算法会怎么做呢?KMP算法会直接把模式串移到匹配失效的位置上,如下图2-2,g处:
图2-2 直接移到匹配失效的位置g处
Ok,咱们下面再看一个例子,如下图2-3/4:
图2- 3/4 另一个例子
如果前面有匹配成功,那移动一位或者几位后,是不可能匹配成功的KMP算法快在于,i只加从不减,只有短串在长串中快速滑动。
代码:
- int kmp_search(char const* src, int slen, char const* patn, int plen, int const* nextval, int pos)
- {
- int i = pos;
- int j = 0;
- while ( i < slen && j < plen )
- {
- if( j == -1 || src[i] == patn[j] )
- {
- ++i;
- ++j; //匹配成功,就++,继续比较。
- }
- else
- {
- j = nextval[j];
- //当在j处,P[j]与S[i]匹配失败的时候直接用patn[nextval[j]]继续与S[i]比较,
- //所以,Kmp算法的关键之处就在于怎么求这个值拉,
- //即匹配失效后下一次匹配的位置。下面,具体阐述。
- }
- }
- if( j >= plen )
- return i-plen;
- else
- return -1;
- }
我们用变量k来代表求得的j_next的最大值,即k表示这S[i]、P[j]不匹配时P中下一个用来匹配的位置,使得P[0…k-1] = P[j-k…j-1],而我们要尽量找到这个k的最大值。如你所见,当匹配到S[i] != P[j]的时候,最大的k为1(当S[i]与P[j]不匹配时,用P[k]与S[i]匹配,即P[1]和S[i]匹配,因为P[0]=P[2],所以最大的k=1)。
图3-2 j_next=1,即最大的k的值为1
如上图3-2,当P[3]!=S[i],而P[0]=P[2](当P[3]!=S[i],而P[0]=P[2],P[2]=S[i-1],所以肯定有P[0]=S[i-1])),所以只需比较P[1]与S[i]就可以了,即k是P可以跳过比较的最大长度,换句话说,就是k能标示出S[i]与P[j]不匹配时P的下一个匹配的位置。
图3-3 第二步匹配中,跳过P[0](a),只需要比较 P[1]与S[4](b)了
也就是说,如上图3-3,在第一次匹配中,就是因为S[3]=P[0],所以在下一次匹配中,只需要比较S[4]=P[1],跳过了几步?一步。那么k等于多少?k=1。即把 P 右移两个位置后,P[0]与S[3]不必再比较,因为前一步已经得出他们相等。所以,此时,只需要比较 P[1]与S[4]了。
接下来的问题是,怎么求最大的数k使得p[0…k-1] = p[j-k…j-1]呢。这就是KMP算法中最核心的问题,即怎么求next数组的各元素的值?只有真正弄懂了这个next数组的求法,你才能彻底明白KMP算法到底是怎么一回事。
那么,怎么求这个next数组呢?咱们一步一步来考虑。
求最大的数k使得P[0…k-1] = P[j-k…j-1],一个直接的办法是对于j,从P[j-1]往回查,看是否有满足P[0…k-1] = P[j-k…j-1]的k存在,而且还要最大的一个k。下面咱们换一个角度思考。
当P[j+1]与S[i+1]不匹配时,分两种情况求next数组(注:以下皆有k=next[j]):
- P[j] = p[k], 那么next[j+1]=k+1,这个很容易理解。采用递推的方式求出next[j+1]=k+1(代码3-1的if部分)。
- P[j] != p[k],那么next[j+1]=next[k]+1(代码3-1的else部分)
稍后,你将看到,由这个方法得出的next值还不是最优的,也就是说是不能允许P[j]=P[next[j]]出现的。ok,请跟着我们一步一步登上山顶,不要试图一步登天,那是不可能的。由以上,可得如下代码:
- //代码3-1,稍后,你将由下文看到,此求next数组元素值的方法有错误
- void get_next(char const* ptrn, int plen, int* nextval)
- {
- int i = 0;
- nextval[i] = -1;
- int j = -1;
- while( i < plen-1 )
- {
- if( j == -1 || ptrn[i] == ptrn[j] ) //循环的if部分
- {
- ++i;
- ++j;
- nextval[i] = j;
- }
- else //循环的else部分
- j = nextval[j]; //递推
- }
- }
图3-13/14 求next数组各值的错误解法