單模式字符串匹配
1. 朴素算法
朴素算法的問題在於不夠智能,有些位置明顯沒有必要進行比較操作,但這個算法無法區分出來,還是繼續比較,浪費了資源。
2. KMP算法
在KMP算法中,引入了前綴函數的概念,從而可以更加精確的知道:當不匹配發生時,應該跳過多少個字符。下面介紹前綴函數。
字符串A = "abcde" B = "ab"。 那么就稱字符串B為A的前綴,記為B ⊏ A。同理可知 C = "e","de" 等都是 A 的后綴,以為C ⊐ A。
這里模式串 P = “ababaca”,在匹配了 q=5 個字符后失配,因此,下一步就是要考慮將P向右移多少位進行新的一輪匹配檢測。朴素算法中,直接將P右移1位,也就是將P的首字符'a'去和目標串的'b'字符進行檢測,這明顯是多余的。通過我們肉眼的觀察,可以很簡單的知道應該將模式串P右移到下圖'a3'處再開始新一輪的檢測,直接跳過肯定不匹配的字符'b',那么我們“肉眼”觀察的這一結果怎么把它用語言表示出來呢?
我們的觀察過程是這樣的:
1. P的前綴"ab"中'a' != 'b',又因該前綴已經匹配了T中對應的"ab",因此,該前綴的字符'a1'肯定不會和T中對應的字串"ab"中的'b'匹配,也就是將P向右滑動一個位移是無意義的。
2. 接下來考察P的前綴"aba",發現該前綴自身的前綴'a1'與自身后綴'a2'相等,"a1 b a2" 已經匹配了T中的"a b a3",因此有 'a2' == 'a3', 故得到 'a1' == 'a3'......
3. 利用此思想,可推知在已經匹配 q=5 個字符的情況下,將P向右移 當且僅當 2個位移時,才能滿足既沒有冗余(如把'a'去和'b'比較),又不會丟失(如把'a1' 直接與 'a4' 開始比較,則丟失了與'a3'的比較)。
4. 而前綴函數就是這樣一種函數,它決定了q與位移的一一對應關系,通過它就可以間接地求得位移s。
這樣的觀察過程並不具有一般性,下面是《算法導論》中對前綴函數的形式化說明:
已知一個模式P[1. . m],模式P的前綴函數是函數π{1,2,. . . , m}->{0,1, 2,. . . ,m-1}並滿足
π[q]=max{k:k<q 且Pk⊐ Pq}
即π[q]是Pq的真后綴P的最長前綴的長度(此是《算法導論》中原話,但不是很好理解,其實就是Pq中即是自己的真后綴,又是自己最長前綴的字符串的最大長度)。下面舉例說明(模式P=ababababca)
i=1時,a真后綴為空;i=2時,ab真后綴為b,不是自己的前綴;i=3時,aba真后綴為a, ab,且a和ab都是aba的前綴,ab最長,故為2;。。。
KMP算法中,如果q+1時發生不匹配,則可以向前移動q-π[q]位。
using namespace std;
/*
when searching a pattern in a string, and mismatch happened, we can skip more chars, instead of going through one by one;
the skip rule is that:
1. if position p mismatched, we need consider the chars in 0- (p-1);
2. whether [0,k-1](prefix substring) matched with [p-k,p-1](suffix substring),if matched, we can align the pattern to p-k;and do comparation from p again.
below function is get the k for different p, more information refer to comments inline;
*/
void get_skippattern( char *pattern, int* next, int len)
{
int pos = 2;
int subStrIndex = 0; // valid prefix candidate substring index;
next[ 0] = - 1; // when 1st char mismatched, always move 1 (p=0, k=-1);
next[ 1] = 0; // when 2nd mismatched, always move 1(p=1, k=0);in fact, if the 2nd char is same as 1st char, we can move 2
while(pos<len)
{
if(pattern[pos - 1] == pattern[subStrIndex]) // one char matched, then continue to match more,
{
subStrIndex++; // prefix substring move ahead;
next[pos] = subStrIndex; // for current position, the k is got;
pos++; // current pos move ahead;
}
else if(subStrIndex> 0) // one substring found, but in the new pos, mismatched;
{
subStrIndex = next[subStrIndex]; // then we need fall back subStrIndex to value that still can be matched;
}
else
{
next[pos] = 0;
pos++;
}
}
for( int i = 0;i<len;i++)
cout<<next[i]<< " ";
}
int KMP_search( char *src, int slen, char *pattern, int plen)
{
int* next = ( int *)malloc( sizeof( int)*slen);
get_skippattern(pattern,next,plen);
int indexInSrc = 0;
int offset = 0;
while((indexInSrc+offset)<slen)
{
if(pattern[offset] == src[indexInSrc+offset])
{
if(offset == (plen- 1))
return indexInSrc;
offset++;
} else
{
indexInSrc += offset-next[offset];
if(next[offset]>- 1)
offset = next[offset];
else
offset = 0;
}
}
return slen;
}
int main ( int argc, char ** argv)
{
// char *pat = "ABCDABDEF";
// char *src = "ABC ABCDAB ABCDABCDABDEF";
char *pat= " ABABETTABABABYUABCD ";
char *src = " ABCDEABCDGABCDETTABCDFABCDETTABCDATYUABCD ";
int index = KMP_search(src,strlen(src),pat,strlen(pat));
cout<< " found pattern in "<<index<<endl;
return 0;
}
3. BM算法
BM算法的特殊之處在於BM是右向左匹配,同時結合壞字符和好后綴兩個規則使得移動距離最大。下面分別介紹壞字符和好后綴規則:
好后綴算法
如果程序匹配了一個好后綴, 並且在模式中還有另外一個相同的后綴, 那
把下一個后綴移動到當前后綴位置。好后綴算法有兩種情況:
Case1:模式串中有子串和好后綴安全匹配,則將最靠右的那個子串移動到好后綴的位置。繼續進行匹配。
Case2:如果不存在和好后綴完全匹配的子串,則在好后綴中找到具有如下特征的最長子串,使得P[m-s…m]=P[0…s]。說不清楚的看圖。
給一些具體的例子
壞字符算法
當出現一個壞字符時, BM算法向右移動模式串, 讓模式串中最靠右的對應字符與壞字符相對,然后繼續匹配。壞字符算法也有兩種情況。
Case2:模式串中不存在壞字符。見圖。
移動規則
BM算法的移動規則是:
將概述中的++j,換成j+=MAX(shift(好后綴),shift(壞字符)),即BM算法是每次向右移動模式串的距離是,按照好后綴算法和壞字符算法計算得到的最大值。
shift(好后綴)和shift(壞字符)通過模式串的預處理數組的簡單計算得到。好后綴算法的預處理數組是bmGs[],壞字符算法的預處理數組是BmBc[]。
下面先解釋這兩個數組的意義:
BmBc 的定義:
1、 字符在模式串中沒有出現:,如模式串中沒有字符p,則BmBc[‘p’] = strlen(模式串)。
2、 字符在模式串中有出現。如下圖,BmBc[‘k’]表示字符k在模式串中最后一次出現的位置,距離模式串串尾的長度。
如果只考慮壞字符,應該移動多少呢?下面的圖里有3個例子:
示例1中,在b和c比較時發生了不match,這時,我們的BmBc[‘c’] = 3,這時我們應該移動多少呢,移動-1,如何計算的呢? BmBc[‘c’] – strlen(pat) +1 + i (index of pattern string)
實例2中,b和a發生不match,這時,BmBc[‘a’] = 6, 應該移動6-7+1+2 = 2;
實例3中,b和y發生不match,這時,BmBc[‘y’] = 7,應該移動7-7+1+1 = 2;
對於這里BmBc的定義,和shift的值的計算是很難讓人理解的,我們是不是可以簡單一點定義BmBc[char] 表示char在pattern中最后出現的位置,如果不出現為pattern的長度,i是不match的index,shift的距離就是BmBc[char]-i。
還有就是,在實例1中,我們真的要去移動-1嗎,其實沒有必要了,如果只用壞字符,你可以想想怎么做;但如果考慮上好后綴就不用額外考慮了。
為了實現好后綴規則,需要定義一個數組suffix[],其中suffix[i] = s 表示以i為起點(包含i,從右往左匹配),與模式串后綴匹配的最大長度,如下圖所示,用公式可以描述:滿足P[i-s, i] == P[m-s, m]的最大長度s。
計算suffix的代碼如下所示:
for (i=m- 2;i>= 0;--i)
{
q=i;
while(q>= 0&&P[q]==P[m- 1-i+q])
--q;
suffix[i]=i-q;
}
有了suffix[i],如何計算BmGc?
bmGs的定義(BmGs數組的下標是數字,表示字符在模式串中位置), BmGs數組的定義,分三種情況:
在這個視角圖1中,在i處發生不匹配,從i開始從右向左搜索子字符串,在視圖2中試圖找到不匹配的字符和子串匹配位置的關系。視圖2中i開始的子串與后綴匹配,那可以知道后綴的長度就是Suffix[i],再往左移動一下就是不匹配的位置了,而這時應該移動的距離是m-1-i,也就是式子bmGs[m-1-suff[i]] = m- 1 – i;
在這種情況下,空白位置發生不匹配時,其好后綴都是最前面的兩個,那么其移動的距離其實跟不匹配的位置 j 沒有關系,只與最好前綴的位置i有關,所以,bmGs[j] = m- 1 – i;
沒有任何子串匹配的時候,那就移動模式串的長度。
舉例如下:
實現代碼如下:
int i, j, suff[XSIZE];
suffixes(x, m, suff);
for (i = 0; i < m; ++i)
j = 0;
if (suff[i] == i + 1)
for (; j < m - 1 - i; ++j)
if (bmGs[j] == m)
bmGs[j] = m - 1 - i;
for (i = 0; i <= m - 2; ++i)
bmGs[m - 1 - suff[i]] = m - 1 - i;
}
下面來完整實現一下BM算法吧:
#include <stdio.h>
#include < string.h>
const int CHAR_COUNT = 26; // only lower case ASCII char
void calculateBmBc( const char *s, int len, int *BmBc)
{
int i= 0;
for(i = 0; i<CHAR_COUNT;i++)
{
BmBc[i] = len;
}
for(i = 0;i<len;i++)
{
BmBc[s[i]- ' a '] = i;
}
}
void calculateSuffix( const char *s, int len, int *suffix)
{
int i = len - 1;
int j = 0;
suffix[len- 1] = len;
for(;i>= 0;i--)
{
j = 0;
while(j<(len- 1) && s[i-j] == s[len- 1-j])
j++;
suffix[i] = j;
}
}
void calculateBmGs( const char *s, int len, int *BmGs)
{
int* suffix = ( int *)malloc( sizeof( int)*len); // new int[len];
int i = 0;
int j = 0;
calculateSuffix(s,len,suffix);
for(i= 0;i<len;i++) // init the array, and also cover case 3
{
BmGs[i] = len;
}
for(i=len- 1;i>= 0;i--)
{
if(suffix[i] == i+ 1) // prefix of the string matched with suffix, case 2
{
for(j = 0;j<len- 1-i;j++)
{
if(BmGs[j] == len)
BmGs[j] = len - 1 - i;
}
}
}
for(i = 0;i<=len- 2;i++) // case 1;
{
BmGs[len- 1-suffix[i]] = len- 1-i;
}
free(suffix);
}
int BMSearch( const char *src, int srclen, const char *pattern, int patlen)
{
int i= 0;
int * BmGs = ( int *)malloc( sizeof( int)*(patlen));
int * BmBc = ( int *)malloc( sizeof( int)*(CHAR_COUNT));
calculateBmBc(pattern, patlen, BmBc);
calculateBmGs(pattern,patlen, BmGs);
for(i = 0;i<(srclen-patlen);)
{
int j = patlen- 1;
while(j>= 0 && src[i+j] == pattern[j]) j--;
if(j < 0)
return i;
else
i += BmGs[j]>(j-BmBc[j])?BmGs[j]:(j-BmBc[j]);
}
free(BmGs);
free(BmBc);
return srclen;
}
int main( int argc, char **argv)
{
int i = BMSearch( " GCAGAGAG ", 8, " AGAG ", 4);
printf( " found pattern at %d ",i);
return 0;
}
4. Sunday算法
多模式字符串匹配
1. AC
2. Wu-Manber算法
Reference:
1. http://blog.csdn.net/zdl1016/article/details/4654061
2. http://blog.csdn.net/iJuliet/article/details/4200771
4. http://hi.baidu.com/kmj0217/blog/item/6f837f2f3da097311e3089cb.html
5. http://www.cs.utexas.edu/users/moore/best-ideas/string-searching/index.html
6. http://blog.sina.com.cn/s/blog_6cf48afb0100n561.html
7. http://blog.csdn.net/sealyao/article/details/4568167
8. http://www.cnblogs.com/v-July-v/archive/2011/06/15/2084260.html