字符串匹配算法——KMP算法淺淺析

1 從最簡單的想法開始

現有兩個字符串textpattern,需要從text中查找是否存在一個連續的字串和pattern相等,如果有的話就返回第一個查找到的字串的起始位置。如果不考慮效率的話,這確實是一個非常簡單的任務,一個最簡單的想法是兩層循環比對,如下圖所示:
在這裏插入圖片描述

2 利用模式串自身的特點來優化

假如模式串有着這樣一個特點,在下標屬於[0, j)的範圍內有完全相等的前綴和後綴:
在這裏插入圖片描述
此時,在與text比對時,如果在下標j處發生失配,那麼我們還需要從頭開始比對嗎?當然不需要,我們可以直接從下標t開始與text中失配處的字符進行比較,還是用圖來說明:
在這裏插入圖片描述
爲什麼可以這麼做,上圖已經描述的很明白了,因爲標記爲黃色的部分是相等的,無需再重複比對。值得一提的是,下標j之前可能存在多對長度相等的前綴和後綴,我們應該選擇其中最長的那一對,這樣不會錯過任何可能的情況。

3 利用next數組優化串匹配

在上一節的例子中,在下標j處發生失配,只需把記錄模式串當前下標的變量的值由j修改爲t然後繼續比對。jt之間是存在着聯繫的,下標j唯一映射到一個值t,這個值的含義是:在[0, j)的範圍內,長度爲t的前綴和等長的後綴完全相同。

既然j對應t,那麼不難想到其它下標也對應着一個值,如果我們把這個對應關係全部找出來並保存在一個名爲next的數組中(next[j]的值爲t),那麼這個數組可以用於優化串匹配算法,即在失配時將下標的值修改爲以它爲索引在next數組中的值,如下面的代碼片段所示:

/* 匹配 */
int i = 0, j = 0;
while ((i < text_len) && (j < pattern_len)) {
     if ((j < 0) || (haystack[i] == needle[j]))
         ++i, ++j;
     else
         j = next[j];
 }

4 構造next數組

那麼問題來了,next數組如何構建?我們可以從next[j]next[j+1]之間的關係入手。如果pattern[t] == pattern[j]則有下圖:
在這裏插入圖片描述
不難看出,此時next[j+1] = next[j] + 1


如果pattern[t] != pattern[j]呢?先不着急回答這個問題,我們先考慮next[t]的值,不妨設其值爲t',則說明在[0, t)的範圍內,長度爲t'的前綴和等長的後綴完全相同,有下圖:
在這裏插入圖片描述
稍加推導即可得到下面的關係:
在這裏插入圖片描述
這時我們發現如果pattern[t'] == pattern[j],那麼next[j+1] = next[t] + 1,如果不能立馬想到這一點,不妨看下圖:
在這裏插入圖片描述


如果pattern[t'] != pattern[j]呢?繼續按照上面的思路看next[t'],就像俄羅斯套娃那樣。構建next數組的代碼如下:

/* 構建next數組 */
vector<int> next(pattern_len, -1);
 for (int i = 0, index = -1; i < needle.size() - 1; ) {
     if ((index < 0) || (needle[i] == needle[index]))
         next[++i] = ++index;
     else
         index = next[index];
 }

需要注意的是next[0]作爲哨兵被初始化爲-1,把實際不存在的pattern[-1]假想爲一個通配符,這樣在邏輯上是合理的,可以用一個實際的例子來體會。

5 完整的程序

藉助leetcode的28號題的測試用例,以下用KMP算法實現字符串匹配的程序可以通過所有用例:

int strStr(string haystack, string needle) {
     if (needle.empty())
         return 0;

     int text_len = haystack.size(), pattern_len = needle.size();
     
     /* 構建next數組 */
     vector<int> next(pattern_len, -1);
     for (int i = 0, index = -1; i < needle.size() - 1; ) {
         if ((index < 0) || (needle[i] == needle[index]))
             next[++i] = ++index;
         else
             index = next[index];
     }

     /* 匹配 */
     int i = 0, j = 0;
     while ((i < text_len) && (j < pattern_len)) {
         if ((j < 0) || (haystack[i] == needle[j]))
             ++i, ++j;
         else
             j = next[j];
     }

     return (j == needle.size()) ? (i - j) : -1;
}

6 參考文獻

[1] 鄧俊輝老師的數據結構課程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章