作者:阮一峰
原文鏈接:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
字符串匹配是計(jì)算機(jī)的基本任務(wù)之一沧卢。
舉例來(lái)說(shuō)花枫,有一個(gè)字符串"BBC ABCDAB ABCDABCDABDE"展鸡,我想知道壁查,里面是否包含另一個(gè)字符串"ABCDABD"篓吁?
許多算法可以完成這個(gè)任務(wù)谭溉,Knuth-Morris-Pratt算法(簡(jiǎn)稱KMP)是最常用的之一。它以三個(gè)發(fā)明者命名堤尾,起頭的那個(gè)K就是著名科學(xué)家Donald Knuth芋绸。
這種算法不太容易理解媒殉,網(wǎng)上有很多解釋,但讀起來(lái)都很費(fèi)勁摔敛。直到讀到Jake Boxer的文章廷蓉,我才真正理解這種算法。下面马昙,我用自己的語(yǔ)言桃犬,試圖寫(xiě)一篇比較好懂的KMP算法解釋。
首先行楞,字符串"BBC ABCDAB ABCDABCDABDE"的第一個(gè)字符與搜索詞"ABCDABD"的第一個(gè)字符攒暇,進(jìn)行比較。因?yàn)锽與A不匹配子房,所以搜索詞后移一位形用。
因?yàn)锽與A不匹配就轧,搜索詞再往后移。
就這樣尾序,直到字符串有一個(gè)字符钓丰,與搜索詞的第一個(gè)字符相同為止躯砰。
接著比較字符串和搜索詞的下一個(gè)字符每币,還是相同。
直到字符串有一個(gè)字符琢歇,與搜索詞對(duì)應(yīng)的字符不相同為止兰怠。
這時(shí),最自然的反應(yīng)是李茫,將搜索詞整個(gè)后移一位揭保,再?gòu)念^逐個(gè)比較。這樣做雖然可行魄宏,但是效率很差秸侣,因?yàn)槟阋?搜索位置"移到已經(jīng)比較過(guò)的位置,重比一遍宠互。
一個(gè)基本事實(shí)是味榛,當(dāng)空格與D不匹配時(shí),你其實(shí)知道前面六個(gè)字符是"ABCDAB"予跌。KMP算法的想法是搏色,設(shè)法利用這個(gè)已知信息,不要把"搜索位置"移回已經(jīng)比較過(guò)的位置券册,繼續(xù)把它向后移频轿,這樣就提高了效率。
怎么做到這一點(diǎn)呢烁焙?可以針對(duì)搜索詞航邢,算出一張《部分匹配表》(Partial Match Table)。這張表是如何產(chǎn)生的骄蝇,后面再介紹膳殷,這里只要會(huì)用就可以了。
已知空格與D不匹配時(shí)乞榨,前面六個(gè)字符"ABCDAB"是匹配的秽之。查表可知,最后一個(gè)匹配字符B對(duì)應(yīng)的"部分匹配值"為2吃既,因此按照下面的公式算出向后移動(dòng)的位數(shù):
移動(dòng)位數(shù) = 已匹配的字符數(shù) - 對(duì)應(yīng)的部分匹配值
因?yàn)?6 - 2 等于4考榨,所以將搜索詞向后移動(dòng)4位。
因?yàn)榭崭衽cC不匹配鹦倚,搜索詞還要繼續(xù)往后移河质。這時(shí),已匹配的字符數(shù)為2("AB"),對(duì)應(yīng)的"部分匹配值"為0掀鹅。所以散休,移動(dòng)位數(shù) = 2 - 0,結(jié)果為 2乐尊,于是將搜索詞向后移2位戚丸。
因?yàn)榭崭衽cA不匹配,繼續(xù)后移一位扔嵌。
逐位比較限府,直到發(fā)現(xiàn)C與D不匹配。于是痢缎,移動(dòng)位數(shù) = 6 - 2胁勺,繼續(xù)將搜索詞向后移動(dòng)4位。
逐位比較独旷,直到搜索詞的最后一位署穗,發(fā)現(xiàn)完全匹配,于是搜索完成嵌洼。如果還要繼續(xù)搜索(即找出全部匹配)案疲,移動(dòng)位數(shù) = 7 - 0,再將搜索詞向后移動(dòng)7位咱台,這里就不再重復(fù)了络拌。
下面介紹《部分匹配表》是如何產(chǎn)生的。
首先回溺,要了解兩個(gè)概念:"前綴"和"后綴"春贸。 "前綴"指除了最后一個(gè)字符以外,一個(gè)字符串的全部頭部組合遗遵;"后綴"指除了第一個(gè)字符以外萍恕,一個(gè)字符串的全部尾部組合。
"部分匹配值"就是"前綴"和"后綴"的最長(zhǎng)的共有元素的長(zhǎng)度车要。以"ABCDABD"為例允粤,
- "A"的前綴和后綴都為空集翼岁,共有元素的長(zhǎng)度為0类垫;
- "AB"的前綴為[A]琅坡,后綴為[B]悉患,共有元素的長(zhǎng)度為0;
∮馨场- "ABC"的前綴為[A, AB]售躁,后綴為[BC, C]坞淮,共有元素的長(zhǎng)度0;
∨憬荨- "ABCD"的前綴為[A, AB, ABC]回窘,后綴為[BCD, CD, D],共有元素的長(zhǎng)度為0市袖;
》戎薄- "ABCDA"的前綴為[A, AB, ABC, ABCD],后綴為[BCDA, CDA, DA, A]凌盯,共有元素為"A"付枫,長(zhǎng)度為1;
〕墼酢- "ABCDAB"的前綴為[A, AB, ABC, ABCD, ABCDA],后綴為[BCDAB, CDAB, DAB, AB, B]二打,共有元素為"AB"县忌,長(zhǎng)度為2;
〖绦А- "ABCDABD"的前綴為[A, AB, ABC, ABCD, ABCDA, ABCDAB]症杏,后綴為[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的長(zhǎng)度為0瑞信。
"部分匹配"的實(shí)質(zhì)是厉颤,有時(shí)候,字符串頭部和尾部會(huì)有重復(fù)凡简。比如逼友,"ABCDAB"之中有兩個(gè)"AB",那么它的"部分匹配值"就是2("AB"的長(zhǎng)度)秤涩。搜索詞移動(dòng)的時(shí)候帜乞,第一個(gè)"AB"向后移動(dòng)4位(字符串長(zhǎng)度-部分匹配值),就可以來(lái)到第二個(gè)"AB"的位置筐眷。
(完)