普通的字符串匹配方法(串的模式匹配方法)
對於普通的串匹配方法,通過簡單的例子進行解析
T: a b a c a a b a c a b a c
W: a b a c a b
會從T[0]跟W[0]進行匹配,如果相等則匹配W的下一個字符,直到出現不相等的情況。簡單丟棄W[0]開始的匹配信息,然後從T[1]開始繼續同W進行匹配,直到串結束或者滿足W長度和T長度差值的循環,如果T中不夠n個W自然可以結束。
具體實例如下所示,返回第一個匹配串及其後邊的字符,時間複雜度O((m-n)*n):
const char* strFind(const char* string1, const char* substring)
{
assert(string1 != NULL && substring != NULL);
int m = strlen(string1);
int n = strlen(substring);
if (m < n)
return NULL;
for (int i = 0; i <= m - n; ++i)
{
int j = 0;
for (; j < n; ++j)//對substring循環,也就是上文中的W
{
if (string1[i + j] != substring[j])
break;
}
if (j == n)
return (string1 + i);
}
return NULL;
}
這種簡單的丟棄前面的信息造成了極大的浪費和低下的匹配效率。
KMP算法是由D.E和V.R同時發現,當前算法的關鍵是利用匹配失敗後的信息,儘量減少模式串與主串的匹配次數以達到快速匹配的目的。具體就是實現next()函數,函數本身包含了模式串的局部匹配信息。
針對第一種基本匹配方法,KMP方法對於每一個模式串會事先計算出模式串內部匹配信息,在匹配失敗時最大移動模式串,以減少匹配次數。
如何計算右移距離成了算法的核心,右移距離的計算也就是next的計算,也就是輸入串前綴後綴對應的匹配值的計算。
什麼是前後綴?
“前綴”指除了最後一個字符以外,一個字符串的全部頭部組合,“後綴”除了第一個字符以外,一個字符串的全部尾部組合。實例如下:
字符串:bread
前綴:b, br, bre, brea
後綴:read, ead, ad, d
部分匹配值的計算:
搜索詞:A B C D A B D
部分匹配值就是“前綴”和“後綴”的最長共有元素的長度。根據以上搜索詞爲例:
A的前綴和後綴都爲空集,共有元素的長度爲零。
AB的前綴爲A,後綴爲B,共有元素長度爲0。
ABC前綴爲A, AB,後綴爲BC,C共有元素長度爲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.
部分匹配的實質是:有時候字符串頭部和尾部會有重複,例如“ABCDAB”之中有兩個“AB”,那麼他的部分匹配值就是2(“AB”的長度)。搜索移動的時候第一個AB向後移動思維(字符串長度減去部分匹配值),第一個AB向後移動四位就可以到達尾部AB的位置。
得到:移動位數 = 字符串長度 – 部分匹配值。字符串長度和部分匹配值的求解分別都是針對已經匹配的模式串子串進行的求解,實例如下:
T: a b a c a a b a c a b a c
W: a b a c a b
T[5]與W[5]出現不匹配,T[0]—T[4]是匹配的。字符串長度爲5,
前綴:a,a b,a b a,a b a c; 後綴:b a c a,a c a,c a,a; 共有元素爲a,長度爲1。
因此可求的移動位數爲4。
Next求解代碼:
void get_nextval(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])
{
++i;
++j;
nextval[i] = j;
}
else
{
j = nextval[j];
}
}
}
字符串查找實例如下所示:
T: a b a c a a b a c a b a c a b a a b b
W: a b a c a b
可求得W的next表爲:
W |
a |
b |
a |
c |
a |
b |
|
0 |
0 |
1 |
0 |
1 |
2 |
關於next求解,加入第二項的a,其實就是求的aba的前綴後綴公共項的最大值。
a b a c a a b a c a b a c a b a a b b
a b a c a b
從第0項開始對比,直到找到不相等的項:發現T[5]和W[5]不相等,也就是說T[0]-T[4]相等,獲取T[0]-T[4]的前綴後綴相同項,最大長度,查表可得爲1.
移動位數 = 5 – 1 = 4;
a b a c a a b a c a b a c a b a a b b
a b a c a b
按照此規律反覆執行。時間複雜度O(m+n)
代碼如下:
int kmp_search(const char*src,int slen,const char* patn, int plen,const int* nextval, int pos)
{
int i = pos, j = 0;
while(i<slen && j<plen)
{
if (j == -1 || src[i] == patn[j])
{
++i;
++j;
}else
{
j = nextval[j];
}
}
if (j >= plen)
{
return i-plen;
}
else
{
return -1;
}
}