KMP算法初始化模式串的next数组

在使用KMP算法处理字符串查找问题的过程当中,可以利用模式串本身的对称性,在移动模式串的时候,尽量多的往后移动,减少无用的查找过程,而模式串本身的对称性一般是保存在一个next数组里面的,下面来讨论下怎么初始化next数组的值。

先来看一下下面这个例子:
示例
申明一下:下面说的对称不是中心对称,而是中心字符块对称,比如不是abccba,而是abcabc这种对称。

分析:
i=0:模式串为m,最长前缀子串和后缀子串都为空,next[0] = 0;
i=1:模式串为mb,最长前缀子串为m,最长后缀子串为b,无对称块,next[1] = 0;
i=2,i=3:同理,next[2] = 0,next[3] = 0;
i=4:模式串为mbwtm,最长前缀子串为mbwt,最长后缀子串为bwtm,对称块为m,长度为1,next[4] = 1;
i=5:模式串为mbwtmb,最长前缀子串为mbwtm,最长后缀子串为bwtmb,对称块为mb,长度为2,next[5] = 2;
i=6:模式串为mbwtmbw,最长前缀子串为mbwtmb,最长后缀子串为bwtmbw,对称块为mbw,长度为3,next[6] = 3;

i=13:模式串为mbwtmbwmbwtmbw,最长前缀子串为mbwtmbwmbwtmb,最长后缀子串为bwtmbwmbwtmbw,对称块为mbwtmbw,长度为7,next[13] = 7;
i=14:模式串为mbwtmbwmbwtmbwt,最长前缀子串为mbwtmbwmbwtmbw,最长后缀子串为bwtmbwmbwtmbwt,对称块为mbwt,长度为4,next[14] = 4;
i=15:模式串为mbwtmbwmbwtmbwtb,最长前缀子串为mbwtmbwmbwtmbwt,最长后缀子串为bwtmbwmbwtmbwtb,无对称块,next[15] = 0;

根据上面的例子,可以总结一下下面的规律,后面出现模串的位置用P替代:

  1. 当前字符的前一个字符的对称块长度为0(next数组对应位置的值为0)时,只需要比较当前字符跟模式串的第1个字符P[0],若相等则当前位置对应的next数组值为1,否则为0;
  2. 按照规律1,如果当前字符的前1个字符的对称块长度为1,此时只需要比较当前字符跟模式串的第2个字符P[1],若相等则当前位置对应的next数组值为2。如果当前字符的前1个字符的对称块长度为2,此时只需要比较当前字符跟模式串的第3个字符P[2],若相等则当前位置对应的next数组值为3。例如当前位置i=6时,可以知道i=5时,next[5] = 2,此时只需要比较模式串位置i=6的字符w跟模式串位置i=next[5]=2位置的字符P[2]=w是否相等(前面的两个字符mb在i=5时已经比较过且相等了),相等话next[6] = next[5] + 1 = 3;
  3. 按照规律2,如果一直想等的话,那就可以一直累加,但是总会出现不相等的时候,如果出现了不相等的情况,并不是说完全没有对称块了,只是对称块的长度变短了,需要重新计算。

针对上面规律3出现的不相等的时候,怎么计算此时next数组对应位置的值,我们以上面例子的i=14为例子来说明:

  1. next[13] = 7,表示模式串的i=0到i=6这一串跟i=7到i=13这一串是完全相等的,设置K=7,K表示前一位置的最长对称块的长度。
  2. i=14时,由于P[14] = t != m = P[7]不相等,所以可以肯定的是i=14位置的next[14]的值肯定比next[13]小,且只能在i=0到i<7这一区间内寻找了.
  3. 因为P[14] != P[7],所以P[0-6]这一段肯定不是对称块了。那么新的对称块是从i=0到i等于多少的位置呢?可以知道P[0-6] == P[7-13],所以可以先计算P[0-6]的对称块长度,即next[K-1]的值。结果是mbw,即P[0-2] == P[4-6],从P[0-6] == P[7-13]可以得到P[4-6] = P[11-13],所以[0-2] == P[11-13],然后比较P[14]是否等于P[3]。
  4. 如果相等,则next[14] = next[K-1] + 1 = next[7-1] + 1 = 4。(如果不相等,则重复步骤3和4。假设P[14] = x != t = P[3],则k = next[k-1] = 3,重复第3步,从P[0-2]里面寻找最长的对称块,此时next的值都为0,所以此时next[14] = 0)。

下面列一下代码并配合注释,帮助理解。

void initNext(const char P[],int next[]) { 
	int i;						//i:模式串下标
    int k;						//k:最大对称块长度 
    int pLen = strlen(P);		//pLen:模式串的长度 
    next[0] = 0;					//模式串的第一个字符的对称块长度为0 

	//for循环,从第二个字符开始,依次计算模式串每一个字符对应的next数组值 
    for (i = 1,k = 0; i < pLen ; i++){
    	//递归的求出P[0]···P[i]的最大的相同的前后缀长度k 
		while(k > 0 && P[i] != P[k]) {
			//当P[i] != P[k]时,参考上面例子中的第三步,需要循环计算k的值
			k = next[k-1];   
		}       
		//如果相等,那么最大相同前后缀长度加1(参考上面说的规律2)
       	if (P[i] == P[k]){
 			k++;
		}
		//赋值k到next数组的i位置
		next[i] = k;
	}
}

注意一下,next 数组考虑的是除当前字符外的最长相同对称快,所以通过上述方法求得的各个位置的最大相同对称块数组next之后,只要稍作变形即可:将next数组中的元素整理往后移动一位,且next[0] = -1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章