本文對應代碼下載地址:
http://download.csdn.net/detail/sun2043430/5323248
本文參考以下兩篇文章,在此表示感謝
http://blog.csdn.net/ijuliet/article/details/4206487
概要說明
Wu-Manber算法用於多模匹配,採用哈希的方式以及BM算法中的壞字符規則達到快速檢索、跳躍式步進的效果。讀者應該先學習BM算法中的壞字符規則再學習Wu-Manber算法。
使用該算法首先有一些約定和一些常數:
每個模式串的長度不能小於某一個值M_VALUE,一般取M_VALUE值爲5。也就是每個模式串的長度都要大於等於5。
BM算法中的壞字符是一個一個的字符,但是Wu-Manber算法中的壞字符是一組一組的,組的長度B_VALUE一般取2或者3(具體使用見下文)。
Wu-Manber算法需要建立兩張哈希表,以及一個前綴單鏈表。
哈希表1爲壞字符組滑動距離表;哈希表2爲關聯表,其中存放前綴單鏈表的頭結點。這樣可以順藤摸瓜得到哈希值相同的所有模式串的信息。
具體建表過程見下文。
預處理建表過程
假設現在要預處理的模式串集合爲:
"abcdef"
"123456"
"ab3456"
"12cdef"
M_VALUE的值爲5,意思是隻預處理每個模式串的前5個字符。同時我們取組長B_VALUE = 2,上面4個模式串的前5個字符取相鄰的2個字符爲一組,得到下面的組合:
"ab","bc","cd","de",
"12","23","34","45",
"ab","b3","34","45",
"12","2c","cd","de".
我們以兩個字符組成一個哈希值,則哈希值的範圍是0到65535。所以我們需要開闢size爲65536的shift哈希表(存儲按照壞字符規則應該滑動的距離)和保存前綴鏈表頭結點的關聯哈希表。
shift哈希表以兩個字符組成的哈希值爲索引,按照這兩個字符離第4、5字符之間的距離來填寫移動的距離,得到下表:
合併在一起爲:
具體的處理過程請參閱代碼。需要注意一些細節,應該先初始化所有表項爲M_VALUE-B_VALUE+1,也就是4。也就是說對於模式串中沒有出現的哈希值,我們應該直接跳過整個字符串的前面4組字符(等同於BM算法中的壞字符規則)。然後對於重複出現的哈希值,在填表時如果發現表中對應項已經填寫了小於當前值的數值,則不能用大的數值去覆蓋已經存在的小的數值。
上面的shift哈希表是指導目標串往後移動的,對於每個模式串的第3,4下標字符組成的哈希值(例如“de”,“45”)在哈希表裏面的對應項填寫的是0,此時表示我們找到有可能匹配的位置(在我們這裏至少是第3,4字符匹配上了),按理說接下來我們就應該比較整個模式串和目標串的對應位置看是否匹配上了,但是可能3,4字符相同的模式串有很多,爲了加速匹配過程,我們又用哈希的方式記錄了每個模式串的最開始的2個前綴字符,在進行逐字符完全匹配之前先檢查前綴值是否一致,對於一致的才繼續進行完全匹配。
我們以第3,4字節組成的WORD類型的哈希值爲下標,在關聯哈希表的對應位置保存鏈表的頭結點,每一個節點的結構如下:
typedef struct _NODE
{
_NODE *pNext; //next node point
const char *pPattern; //pattern string
WORD wPrefix; //prefix value(0,1 byte)
}NODE, *PNODE;
一開始,關聯哈希表中的所有項全部初始化爲0,然後每處理一個模式串則new一個節點,設置好該節點的模式串信息,節點的pNext域填寫爲關聯哈希表對應項的數值。最後將結點的指針填寫在關聯哈希表的對應項處。(也就是形成一個單鏈表,最後加入的節點元素在鏈表頭部,而關聯哈希表中保存的是該單鏈表的頭節點指針)這樣,shift哈希表、關聯哈希表 及 前綴單鏈表 全部建好之後,它們之間的關係如下圖:
可以看出關聯哈希表比較稀疏,其中的空項較多。
Wu-Manber算法匹配過程
經過預處理之後,匹配過程從目標串的開頭進行,首先用目標串的第3,4字節組成的哈希值在shift哈希表進行索引,如果得到的值不爲0,則根據得到的值將目標串往後滑動(目標串的比較位置往後增加),如果在shift哈希表中查詢到的值爲0,則轉入關聯哈希表,得到前綴單鏈表的頭節點,依次遍歷該單鏈表,先比較頭2個字符的前綴值是否一樣,如果一樣就開始真正的逐字符比較,判斷是否匹配上了對應的模式串;如果前綴值不同則繼續看單鏈表的下一個節點。如果想查找到所有的匹配情況,則在遍歷完單鏈表之後將目標串的下標加1,再繼續進行該匹配過程。
總結來說,匹配過程爲:
- 查shift哈希表進行目標串的移動。
- 如果shift哈希表對應項爲0,則根據關聯哈希表得到前綴單鏈表頭結點。
- 遍歷前綴單鏈表,對於前綴相同的模式串進行完整匹配檢查,得到是否匹配的結果。
算法補充說明
本文演示的Wu-Manber算法使用的分組長度B_VALUE值爲2,對於2個字節計算哈希值方面會非常簡單(直接並在一起組成一個WORD值),哈希表佔用的內存大小也不是很大(65536字節爲64K、65536*4字節爲256K、每個鏈表節點佔10個字節,考慮內存對齊在32位系統下佔12個字節,節點個數爲模式串的數量)。
如果取分組長度B_VALUE值爲3,則計算哈希值的方式要重新設計,不能簡單的採用3字節合併的方式(2的24方字節佔16M內存)。哈希算法的選取直接決定了哈希表的大小。採用3字節的分組長度哈希過程會複雜一些,但在匹配過程中遇到的可能匹配上的情況會更少一些,具體B_VALUE值是選取2還是3,我在一個代碼的註釋裏面看到如下說明:
// Wu Manber paper suggests B is 2 or 3
// small number of patterns, use B=2, use an exact table
// for large number of patterns, use B=3 use compressed table (their code uses 400 as a cross over )
就是說模式串數量少時,設定B=2;模式串數量大時設定B=3,在Wu-Manber算法論文代碼中以400做爲轉折點。
代碼說明
在我上傳的代碼中,有一個別人實現的C++ Wu-Manber算法類 和 我自己實現的C方式Wu-Manber算法代碼。
TestWuManber函數是測試Wu-Manber算法類的,TestWuManber2是測試我寫的C代碼的。感興趣的讀者可以對比閱讀這兩個實現,裏面的B_VALUE選值不一樣。
在目標串char text[] = "text is abcdef 123456 abx456 12xdef 12cdef ab3456 ab3457"中查找以下下幾個模式串
"abcdef"
"123456"
"ab3456"
"12cdef"
執行結果如下:
0 1 2 3 4 5 6
0123456789012345678901234567890123456789012345678901234567890
text is abcdef 123456 abx456 12xdef 12cdef ab3456 ab3457
Find at 8, abcdef
Find at 15, 123456
Find at 36, 12cdef
Find at 43, ab3456
本文對應代碼下載地址:
http://download.csdn.net/detail/sun2043430/5323248