字符串匹配是計算機的基本任務(wù)之一。
舉例來說屉来,有一個字符串"BBC ABCDAB ABCDABCDABDE"务傲,我想知道,里面是否包含另一個字符串"ABCDABD"夯到?
許多算法可以完成這個任務(wù)嚷缭,Knuth-Morris-Pratt算法(簡稱KMP)是最常用的之一。它以三個發(fā)明者命名黄娘,起頭的那個K就是著名科學(xué)家Donald Knuth峭状。
這種算法不太容易理解,網(wǎng)上有很多解釋逼争,但讀起來都很費勁优床。直到讀到Jake Boxer的文章,我才真正理解這種算法誓焦。下面胆敞,我用自己的語言,試圖寫一篇比較好懂的KMP算法解釋杂伟。
首先移层,字符串"BBC ABCDAB ABCDABCDABDE"的第一個字符與搜索詞"ABCDABD"的第一個字符,進行比較赫粥。因為B與A不匹配观话,所以搜索詞后移一位。
因為B與A不匹配越平,搜索詞再往后移频蛔。
就這樣,直到字符串有一個字符秦叛,與搜索詞的第一個字符相同為止晦溪。
接著比較字符串和搜索詞的下一個字符,還是相同挣跋。
直到字符串有一個字符三圆,與搜索詞對應(yīng)的字符不相同為止。
這時避咆,最自然的反應(yīng)是舟肉,將搜索詞整個后移一位,再從頭逐個比較牌借。這樣做雖然可行度气,但是效率很差,因為你要把"搜索位置"移到已經(jīng)比較過的位置膨报,重比一遍。
一個基本事實是,當(dāng)空格與D不匹配時现柠,你其實知道前面六個字符是"ABCDAB"院领。KMP算法的想法是,設(shè)法利用這個已知信息够吩,不要把"搜索位置"移回已經(jīng)比較過的位置比然,繼續(xù)把它向后移,這樣就提高了效率周循。
怎么做到這一點呢强法?可以針對搜索詞,算出一張《部分匹配表》(Partial Match Table)湾笛。這張表是如何產(chǎn)生的饮怯,后面再介紹,這里只要會用就可以了嚎研。
已知空格與D不匹配時蓖墅,前面六個字符"ABCDAB"是匹配的。查表可知临扮,最后一個匹配字符B對應(yīng)的"部分匹配值"為2论矾,因此按照下面的公式算出向后移動的位數(shù):
移動位數(shù) = 已匹配的字符數(shù) - 對應(yīng)的部分匹配值
因為 6 - 2 等于4,所以將搜索詞向后移動4位杆勇。
因為空格與C不匹配贪壳,搜索詞還要繼續(xù)往后移。這時蚜退,已匹配的字符數(shù)為2("AB")闰靴,對應(yīng)的"部分匹配值"為0。所以关霸,移動位數(shù) = 2 - 0传黄,結(jié)果為 2,于是將搜索詞向后移2位队寇。
因為空格與A不匹配膘掰,繼續(xù)后移一位。
逐位比較佳遣,直到發(fā)現(xiàn)C與D不匹配识埋。于是,移動位數(shù) = 6 - 2零渐,繼續(xù)將搜索詞向后移動4位窒舟。
逐位比較,直到搜索詞的最后一位诵盼,發(fā)現(xiàn)完全匹配惠豺,于是搜索完成银还。如果還要繼續(xù)搜索(即找出全部匹配),移動位數(shù) = 7 - 0洁墙,再將搜索詞向后移動7位蛹疯,這里就不再重復(fù)了。
下面介紹《部分匹配表》是如何產(chǎn)生的热监。
首先捺弦,要了解兩個概念:"前綴"和"后綴"。 "前綴"指除了最后一個字符以外孝扛,一個字符串的全部頭部組合列吼;"后綴"指除了第一個字符以外,一個字符串的全部尾部組合苦始。
"部分匹配值"就是"前綴"和"后綴"的最長的共有元素的長度寞钥。以"ABCDABD"為例,
- "A"的前綴和后綴都為空集盈简,共有元素的長度為0凑耻;
- "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采郎;
- "ABCDABD"的前綴為[A, AB, ABC, ABCD, ABCDA, ABCDAB]千所,后綴為[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的長度為0蒜埋。
"部分匹配"的實質(zhì)是淫痰,有時候,字符串頭部和尾部會有重復(fù)整份。比如待错,"ABCDAB"之中有兩個"AB"籽孙,那么它的"部分匹配值"就是2("AB"的長度)。搜索詞移動的時候朗鸠,第一個"AB"向后移動4位(字符串長度-部分匹配值)蚯撩,就可以來到第二個"AB"的位置础倍。
-- 阮一峰 - http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html