1 從最簡單的想法開始
現有兩個字符串text
、pattern
,需要從text
中查找是否存在一個連續的字串和pattern
相等,如果有的話就返回第一個查找到的字串的起始位置。如果不考慮效率的話,這確實是一個非常簡單的任務,一個最簡單的想法是兩層循環比對,如下圖所示:
2 利用模式串自身的特點來優化
假如模式串有着這樣一個特點,在下標屬於[0, j)
的範圍內有完全相等的前綴和後綴:
此時,在與text
比對時,如果在下標j
處發生失配,那麼我們還需要從頭開始比對嗎?當然不需要,我們可以直接從下標t
開始與text
中失配處的字符進行比較,還是用圖來說明:
爲什麼可以這麼做,上圖已經描述的很明白了,因爲標記爲黃色的部分是相等的,無需再重複比對。值得一提的是,下標j
之前可能存在多對長度相等的前綴和後綴,我們應該選擇其中最長的那一對,這樣不會錯過任何可能的情況。
3 利用next數組優化串匹配
在上一節的例子中,在下標j
處發生失配,只需把記錄模式串當前下標的變量的值由j
修改爲t
然後繼續比對。j
和t
之間是存在着聯繫的,下標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] 鄧俊輝老師的數據結構課程