怎么理解KMP算法中的next数组(为甚有时候加一有时候不加一?)

KMP算法怎么来的和找规律,以及对于BF算法他的主要区别就不再赘述我们,我们来说一下怎么用最快的速度加上已知规律找到给定字符串的next数组(C语言中和java中都适用):
我们已经找到了部分规律:next数组中的值可能和已匹配部分字符串的前缀和后缀有关系,我们通过例子和解释来了解一下以后什么具体点的关系:

假设:模式串T为:a b a b c

对于各个不匹配位置的已匹配位可能有:
a //第一个b位置不匹配
a b //第二个a位置不匹配
a b a //第二个b位置不匹配
a b a b //c位置不匹配
根据kmp算法给出的结论(这个是以C语言来说的,因为T1表示的是第一个字符,跟java中第一个字符索引为0不同但是往下边看如何求next数组就能找到和java的区别以及可以用java写出来):next数组
其中的当j=4时T1T2=T2T3和当j=5时T1T2T3=T2T3T4分别为对应的已匹配的字符串的最长前缀和最长后缀,然后在有次长前缀和次长后缀比较,进而还有次次长前缀和次次长后缀进行比较;所以我们暂且认为是最长可以相等的前缀和后缀之间的关系 接下来通过上边例子来看他们之间到底什么关系:(我们用len来表示可以找到的最长的前缀和后缀相等时候的长度)
a // len=0
a b //len=0
a b a //len=1 T1 = T3 长度为1
a b a b //len=2 T1T2 = T3T4 长度为2;
接下来我们通过主管判断一下这个字符串对应的next数组为:
a b a b c
0 1 1 2 3
那我们可以发现 除了第一个字符对应的next,其他位对应的都是我们求得的对应位置上len +1;那我们来研究一下为什么是这种情况;
举个例子: 当在第二个b位置不匹配的时候,此时我们已经匹配部分为 a b a我们如果想要让T1位置的a 通过移动来正好匹配上T3位置上的那么需要我们T1的下一个位置移动到此时不匹配的位置,而正好T1的一下个位置就是len的长度再加一;这并不是巧合;你可以带入a b a b:想要第一个a b 匹配上第二个a b 我们需要让第一个b的后一个位置对应着现在不匹配的位置的地方,第一个b后一个位置就是len+1;(这里是对应着C语言来说的是第一个字符的索引为1,如果对应着java就是第一个索引是0就正好不加一就是对应字符的索引位置);所以说通过这种规律我们可以很快找到给定字符串的next数组,并且理解了为什么加一为什么不加一;
理解之后我们来看一下为什么next给定赋值式子是规定了j=1时next[1]为0:
当j=1时,对应的就是第一个字符,如果第一个字符都不匹配了我们是不是不需要像其他字符不匹配一样跳来跳去的吧?第一个字符都不匹配我们需要移动主串S的对应位置进行加一了(后移一位了),所以说这个0就是标志一下是否是第一个字符不匹配(因为第一个字符不匹配跟其他字符不匹配做的动作稍微有点不同),意思是这个0我们是完全可以换掉的,因为他就是一个标志么(会在下边贴出如果不用0标记的代码);还有就是如果j=2(就是对应于“其他情况”),我们只能有一个动作就是把模式串T后移一位,换句话说就是第二个字符串不匹配我们只能让第一个字符来进行匹配,我没有其他选择,所以说当我们模式串T长度大于二的时候第二位一定会是1,就是对应我第一个字符的索引位置;
我们通过这种找到的关系就可以手动写给定模式串的next数组了,但是也避免字符串太多人看不过来就可以写一个程序来运行代码:
C语言对应算法:

void get_next( String T, int *next )
{
	j = 0;
	i = 1;//初始化两个相邻的索引
	next[1] = 0;  //确定
	while( i < T[0] )  //长度足够
	{
		//j==0 初始化j=0  当我们模式串T 的第一个字符都不匹配的时候
		if( 0==j || T[i] == T[j] )
		{
		//必须要先++在赋值原因是这是已匹配字符段的比较我们要将结果赋值给那个不匹配的位置(跟手写时候讲len为什么要加一结果一样)
			i++; 
			j++; 
			next[i] = j; 
		}
		else
		{
			j = next[j]; 
		}
	}
}

我们上边说过了因为java代码字符串首个字符是从零开始的所以说我们如果要将他修改为java代码只需要将对应索引位置减一即可;对应的java代码(完整带测试代码):

public class KMP {
public int[] getNext(String pat) {
			int[] next = new int[pat.length()];
			int j = -1;
			int i = 0;
			next[0] = -1;
			while(i<pat.length()-1) {
				if(j==-1||pat.charAt(i)==pat.charAt(j)) {
					i++;
					j++;
					next[i] = j;
				}else {
					j = next[j];
				}
			}
			int[] copyOf = Arrays.copyOf(next, next.length);
			for(int o=0;o<copyOf.length;o++) {
				copyOf[o] = copyOf[o]+1;
			}
			System.out.println("正常C语言中应该是:"+Arrays.toString(copyOf));
			System.out.println("用我们java来写是:"+Arrays.toString(next));
			return next;
		}
		public boolean contains(String des,String pat) {
		int[] next = getNext(pat);
		int i = 0;
		int j = 0;
		while(i<des.length()&&j<pat.length()) {
			if(j==-1||des.charAt(i)==pat.charAt(j)) {
				i++;
				j++;
			}else {
				j = next[j];
			}
		}
		return j==pat.length();
	}
	public static void main(String[] args) {
		KMP kmp = new KMP();
//		System.out.println(kmp.contains("123456", "abaabcac"));
		System.out.println(kmp.getNext1("abaabcac"));
	}
}

我们之前说过这个j=1时,next[1]=0只是一个标志用来区分是否是因为第一个字符不匹配造成的;所以说我们完全可以把这个标志位做一个更改:

void get_next( String T, int *next )
{
	j = 1;
	i = 2;  
	next[1] = -10000;
	while( i < T[0] )
	{
		if(j==-10000)
		{
			i++;
			j = 1;
			next[i] = j;
	//这个地方不知道C语言中有没有continue大概就是这个意思;对应java的很好写了直接就是continue;
			countinue;
		}
		if(T[i] == T[j] )
		{
			i++; 
			j++; 
			next[i] = j; 
		}
		else
		{
			j = next[j]; 
		}
	}
	
	// 因为前缀是固定的,后缀是相对的。
}

java中因为你标志位的改变相应用的时候判断也是需要改变的;

public int[] getNext1(String pat) {
		int[] next = new int[pat.length()+1];
		int j = 0;
		int i = 1;  
		next[0] = -10000;
		while( i < pat.length()-1 )
		{
			if(j==-10000)
			{
				i++;
				j = 0;
				next[i] = j;
				continue;
			}
			if(pat.charAt(i) == pat.charAt(j) )
			{
				i++; 
				j++; 
				next[i] = j; 
			}
			else
			{
				j = next[j]; 
			}
		}

当前这个KMP算法的next数组还是可以优化的,比如说在求得的 aaaaax中,如果最后一个a不匹配我们能不能让他直接s串位置向后移?(因为根据上边分析求出来的next数组是012345)还是直接根据代码理解吧:

void get_nextval(SString T, int  &nextval[ ] )
{ //next函数修正值存入数组nextval
       i=1; j=0;
       nextval[1]=0;
       while(i<T[0] )
       {     if(j= = 0||T[i]= =T[j] ) 
             {      ++i;   ++j;
             //这里多了一个判断
                     if(T[i]!=T[j] )    nextval[i]=j;
                     else     nextval[i]=nextval[j]; 
             }
             else     j=nextval[j]; 
       }
}

以上。

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