1. 程式人生 > >字串匹配:求給定字串的next陣列以及KMP演算法

字串匹配:求給定字串的next陣列以及KMP演算法

一、理解字串的next陣列值

next陣列值的概念涉及到字串匹配的問題,比較抽象,先介紹一些預備知識:

(1)主串和模式串

       例如我們想知道一個字串是否包含另一個字串時,如串S="bbc abcdab abcdabcdabde"中是否包含串s="abcdab",那麼S稱為主串,s稱為模式串。解決這個字串匹配問題的演算法就是KMP演算法。KMP演算法與next陣列關係密切。有關KMP演算法:KMP演算法連結

(2)字串的字首和字尾、字串的字首和字尾的最大長度

給定字串"bread",

         字首:是指除最後一個字元外,剩餘字元的全部頭部組合。"b,br,bre,brea",共有四個元素。

         字尾:是指除第一個字元外,剩餘字元的全部尾部組合。"read,ead,ad,d",共有四個元素。

         字串的字首和字尾的最大長度是指:字首和字尾相同元素的最大長度。舉例說明。S=ABCDAB,下面是稱為S的子串

 A:字首和字尾都是空集,沒有相同元素,長度為0

 AB:字首[A],字尾是[B],沒有相同元素,長度為0

 ABC:字首是[A, AB],字尾是[BC, B],長度為0

ABCD:字首是[A, AB, ABC],字尾是[BCD, CD, D],沒有相同元素,長度為0

ABCDA:字首是[A, AB, ABC,ABCD],字尾是[BCDA, CDA, DA, A],共同元素是[A],長度為1

ABCDAB:字首是[A, AB, ABC,ABCD, ABCDA],字尾是[BCDAB, CDAB, DAB, AB, B],共同元素是[AB],長度為2

(3)next[j]=k的含義

利用KMP演算法將模式串從第一個位置開始自坐向右移動逐個匹配主串時,當模式串第j個字元與主串第i個字元失配,為了提高匹配效率,避免回溯,則將模式串向右移動j-next[j]位,

然後再繼續匹配。j叫做已匹配值。next[j]=k的含義是模式串在第j個字元失配時,主串位置i保持不變,將模式串中第k個字元與主串的第i個字元對齊之後,再進行匹配。

注意:這裡所說的字串的第j個字元是指的第j+1個位置,即預設第1個位置對應的是第0個字元

next[j]的值=前第0到第j-1個字元組成的子串的字首和字尾的最大長度

next陣列有預設是next[0]=-1開頭,也有next[0]=0開頭,後者只需要將以-1開頭的next每個元素加1即可,以下是以next=-1開頭討論。

舉例說明

如上圖所示,此時在第j位字元D時失配,j=6,已匹配值m=6,找對應的前j位的模式子串(第0到第j-1個字元),取每一個子串字首字尾的最大長度作為整個子串的最大長度,結果為2,所以

第j位D對應的next[j]=2,整個字串向右移動6-2=4位,或者說是將第next[j]個字元與失配位置對齊後(第2個字元對應的是C),然後再進行匹配。


(4)最大長度與next陣列的關係

next陣列
pattern A B C D A B D
j 0 1 2 3 4 5 6
最大長度 0 0 0 0 1 2 0
next[j](-1開頭) -1 0 0 0 0 1 2
next[j](0開頭) 0 1 1 1 1 2 3

二、手動求給定字串的next陣列

1、s="aaab" 對應的next值為{-1,0,1,2}或者分別加1為{0,1,2,3},解釋如下:

第一位預設為0,next[0]=-1

第二位,前面的子串只有a,字首和字尾為空集,最大長度=0,nex[1]=0

第三位,子串[a,aa],a的子串長度為0,aa的字首和字尾共有的元素"a",長度=1,next[2]=1

第四位,子串[a,aa,aaa],直接算aaa的字首和字尾的最大長度len("aa")=2,next[3]=2

2、 s="abaabcac"對應的next值為{-1 0 0 1 1 2 0 1} or {0 1 1 2 2 3 1 2}

第一位next[1]=-1

第二位b對應的子串只有a,長度為0,next[2]=0

第三位a對應的子串a,ab,ab的字首字尾無共有元素,長度為0,next[3]=0

第四位a對應的模式子串,這裡直接算aba,aba的字首字尾共有元素a,長度為1,next[4]=1

第五位b對應的模式子串,直接算abaa,字首[a,ab,aba],字尾[baa,ba,a],共有元素a,next[5]=1

第六位c對應的子串,直接算abaab,字首[a,ab,aba,abaa],字尾[baab,aab,ab,b],共有元素ab,長度為2,next[6]=2

第七位a對應的子串,直接算abaabc,其字首和字尾都沒有共有元素,next[7]=0

第八位c對應的子串,直接算abaabca,其字首和字尾只有共有元素a,next[8]=1

三、next陣列C++程式碼實現即遞迴求next[j]

這裡的next陣列的解法是根據定義求取,僅給出程式碼不作解釋:

#include<iostream>
#include<string>

using namespace std;

//get the next array of pattern string
void getnext(string &p,int next[])
{
	int length=p.length();
	//int next[length];
	int j=0,k=-1;
	//初始化next陣列第一個值,k表示next陣列的值,即失配字元j之前的子串中字首和字尾最大的長度值
	next[0]=-1;
	//計算next[j]
	while(j<length-1)
	{	
		//根據定義:k=next[k],p[k]==p[j]時,next[j+1]=next[k]+1,
		//且保證p[j+1]!=p[next[j+1]]則匹配成功,否則繼續遞迴尋找
		if (k==-1 || p[j]==p[k])
		{
			j++;
			k++;
			if(p[j]!=p[k])//此時j=j+1,k=next[k]+1
				next[j]=k;
			else
				next[j]=next[k];
		}
		else
			k=next[k];
	}
	//return next;
}
void main()
{
	string p="abc";
	int * next = new int[p.length()];
	getnext(p, next);
	for(int i=0;i < p.length(); i++)
		cout<<p[i]<<'\t'<<next[i]<<endl;
	delete []next;
}

四、KMP演算法實現

直接上程式碼,返回的是模式串匹配成功時在主串的位置

#include<iostream>
#include<string>

using namespace std;

//get the next array of pattern string
void getnext(string &p,int next[])
{
	int length=p.length();
	//int next[length];
	int j=0,k=-1;
	//初始化next陣列第一個值,k表示next陣列的值,即失配字元j之前的子串中字首和字尾最大的長度值
	next[0]=-1;
	//計算next[j]
	while(j<length-1)
	{	
		//根據定義:k=next[k],p[k]==p[j]時,next[j+1]=next[k]+1,
		//且保證p[j+1]!=p[next[j+1]]則匹配成功,否則繼續遞迴尋找
		if (k==-1 || p[j]==p[k])
		{
			j++;
			k++;
			if(p[j]!=p[k])//此時j=j+1,k=next[k]+1
				next[j]=k;
			else
				next[j]=next[k];
		}
		else
			k=next[k];
	}
	//return next;
}

//返回主串中模式串與其匹配成功時第一個字元位置
int Kmpsearch(string &src,string &p,int k,int next[])
{
	
	//從主串的第k個字元開始搜尋,
	int index_s = k, index_p = 0;
	int length_s = src.length();
	int length_p = p.length();
	
	while(index_s < length_s && index_p < length_p)
	{
		if (index_p == -1 || p[index_p] == src[index_s])
		{
			index_s++;
			index_p++;
		}
		//如果index_s != -1,且src[index_s] != p[index_p],即匹配失敗,則令index_s保持不變,index_p=next[index_p];
		else
			index_p = next[index_p];
	}
	if (index_p==length_p)
		return index_s-index_p;
	else
		return -1;
}

void main()
{
	string src="abaacabcd",p="abc";
	//cout<<"please input two string:"<<endl;
//	cin>>src;
	//cin>>p;
	int k=0;	
	int * next = new int[p.length()];
	getnext(p, next);
	for(int i=0;i < p.length(); i++)
		cout<<p[i]<<'\t'<<next[i]<<endl;
	k = Kmpsearch(src,p,k,next);
	cout<<k<<endl;
	delete []next;
}

參考文獻: