KMP 演算法並非字串查詢的優化

2021-07-24 03:33:13 字數 2790 閱讀 6267

演算法書和資料結構書對kmp演算法多有介紹,稱只需對字串掃瞄一遍不需回溯云云.然而,它恐怕只應該作為一種思想存在;用於實際的字串查詢並不理想.要費勁心血實現和優化它,才能在特定的字串上略微超過(也可能略微遜過)std::search.

kmp演算法的基本思想,是利用需要匹配字串的自身資訊來避免回溯.(這裡討論的演算法是以c/c++為程式語言,因此下標索引以0開始) 

例如:字串pat=」abcabcde」,裡面第二段的abc和pat開頭的字元是匹配的.

假如我們有個要查詢的字串text=」abcabd...」,在比較到text的'd'處(text[5])也即到了pat的第二個'c'(pat[5])時比較失敗,一般的字串查詢又得從text[1](即'b')開始查詢pat.而kmp演算法知道pat的第二段abc匹配自身的開頭字串,它會對pat儲存如下next表:0 0 0 1 2 3 0 0數字代表匹配失敗後應該從pat的第多少個位置開始比較.我們這裡在pat[5]處失敗,而前面的pat[0...4]都比較成功,因此先看看pat[5]的上乙個成功位置的next資訊:next[4]=2,這代表應該從pat[2]處重新開始比較(即說明有2個字元不需要比了),就不需要跳到text[1]處重新啟動查詢,這時的pat[0...1]為」ab」,text的'd'的前面兩個也是」ab」,只需要開始比較text[5]和pat[2],kmp說的無回溯意義正是如此:不需要回溯text的比較位置.如果pat[2]又失敗了,next[1]=0.那麼text[5]與pat[0]開始比較,不成功的話text遞增到text[6],一直遞增到成功為止,成功的話當然pat也開始遞增.

偽碼如下:

templatei find(i beg,i end)

else if (patpos)//根據next表資訊調整模式串要比較的位置

patpos=nexttable(--patpos);

else

++beg; }

return end;

}

我用微軟的wingdi.h標頭檔案測試,方法是讀入後自加到1m以上,在裡面找」in hdc, in uint」」polypolyline」這樣的串若干次.

實測效能非常低,既比不過std::string::find,也比不過std::search.

於是我進行了第一次優化.像很多書本的作者正確指出的那樣,一般的程式設計師對該優化的地方常常估計錯誤:

請看前面說的pat[5]失敗的地方,它跳轉到pat[2]開始比較,然而pat[2]比較必定失敗,為什麼?因為pat[5]==pat[2]='c',pat[5]失敗當然pat[2]也會失敗,我的優化移除此類情況,也即next表:0 0 0 1 2 3 0 0優化後變為:0 0 0 0 0 3 0 0 .

興沖沖的重新編譯執行換來的卻是些微的提公升.顯然這不是影響效能的地方.

分析良久後也沒找到應該改進的地方,因為std::search的**比較平凡.最後抱著試試看的心理,把**替換了:

templatei find(i beg,i end)

}return end;

}

帶來了巨大的速度提公升,使得kmp查詢的速度大大提公升,在gcc上比string.find快,在vs 2008比std::search快了(恩,換句話說就是比gcc的std::search慢,比vs 2008的string.find慢...)

恩,總算發現效能低下的真正原因了:在一大塊文字中找少量匹配的文字,最好是先濾掉不匹配的.對於不匹配的情況,std::find需要的指令更少;因此先用它來跳過不匹配的字元後在進入kmp比較演算法的核心會帶來可觀的速度提公升.

那麼接下來還有什麼高階手段使得kmp演算法大大改進嗎?我嘗試了很久.然而很遺憾,沒有什麼再值得慶祝的優化了:它們增加了10倍的煩惱,換來一點點的效能提公升.比方說:對於pat=」abcabcde」,它的前置串」abcab」的next值都為0,意味著可以先提前用簡單的**去比較」abcab」,一旦比較失敗又繼續從失敗處開始找而不需要去檢視next表.因此我的實際**是用findfront_代替了std::find來幹這事.這讓kmp演算法提公升了一點點效能,付出了n天思考和除錯的時間.

最終的成型演算法是這樣的結果:比gcc自帶的string::find要快,偶爾(通過對比較文字串的大小和比較次數調整)快於gcc的std::search;比vs 2008的std::search快,被vs 2008的string::find秒殺....

結論:1.簡單的**編譯器容易優化.也適合現代處理器的**執行技術和cache.

2.日常用的要查詢的字串不會有多少適合kmp查詢:」abcabcabcabc」的next表是什麼值?優化後應該全為0!我對著wingdi.h看了幾遍才找到了像」wingdiapi bool winapi」,」patpaint」這些歪瓜劣棗,它們的next表優化後也只有一兩個地方有值.kmp演算法應該適用在文字中大量字串重複且要找的子串也自身重複的情況.

3.去實現kmp查詢不如用std::search.

花絮:gcc的string::find很低效;因為它們就是用char_trait的eq乙個個去找,vs2008的是用memchr先找到乙個再去比較整個串.也許unix世界習慣了用正則庫根本不怎麼用標準庫自帶的.

不過gcc的string::find低效是相對自己的其它庫函式而言.它編譯的測試**普遍比vs2008的快上一倍以上.

vs2008的std::search比較低效;就是平凡地乙個字元乙個字元對比;而gcc的std::search先用std::find找到第乙個相等的字元後再去比較整個串.(看這裡和string::find情況反過來了)

vs2008的std::find很平凡,只是對char的情況特化成memchr;gcc的find對隨機迭代器做了優化,迴圈展開成4個4個地比較.

字串查詢 KMP演算法 優化next陣列

include include int getnextval char partner,int next else else return 0 int kmp char str,char partner else if j plen return pos int main 如果碰到匹配串類似 aaa...

字串查詢演算法kmp

字串查詢最簡單的方法就是乙個乙個地 滑動 查詢。這樣查詢演算法複雜度可定很高,假設pattern的長度為m,文字txt的長度為n,那麼演算法複雜度為o m n m 1 kmp模式搜尋演算法 kmp knuth morris pratt 我只認識knuth,大名鼎鼎的高納德老頭子嘛。kmp演算法的基本...

字串查詢演算法kmp

給定乙個文字串s,和乙個匹配串p,要求查詢p第一次在s中出現的位置。常見的方法如暴力搜素,逐個匹配s i p j 若匹配,下標後移。不匹配,i回溯到這次匹配前的下一位置,j置為0,重新匹配。最壞情況,時間複雜度o n m int violencesearch char s,char p else i...