字串匹配:求給定字串的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陣列的關係
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;
}
參考文獻: