前綴函數與KMP算法



title: 前綴函數與KMP算法
date: 2020-08-05
tags:

  • 算法
  • 字符串
  • OI

categories:

  • 技術

因為大二的時候全程划水,導致我對KMP只聽說過名字。老師似乎都沒展開講,我記得是有一節下課時說這個算拓展內容,可以自己回去研究,所以我印象中還蠻難的。

前段時間在廖雪峰的網站重新學了一遍,自己代碼實現了一下,感覺還蠻簡單的,與印象中不符,有點奇怪。

直到今天在Leetcode上用KMP解一道字符串匹配居然超時了,才發現不對勁。

仔細查閱后才意識到,KMP的難點根本不在於算法本身,而在於構建前綴函數的過程。廖雪峰的網站把這個前綴函數是什么講得很清晰了(也就是PMT,部分匹配表),但是如果按照朴素的想法來構建這個東西,會發現時間復雜度特別高:

//朴素方法,j即最長公共前后綴的長度
vector<int> pi(s.size());
for (int i = 1; i < s.size(); ++i) {
    for (int j = i; j >= 0; --j) {
        if (s.substr(0, j) == s.substr(i - j + 1, j))
            pi[i] = j;
            break;
    }
}

雙重循環內再加子串的比較,時間復雜度直接來到O(N^3)。盡管構建后的表格能加速后面的比較,但是構建表格本身消耗立方時間的話會得不償失,一定要優化這個方法。

看了半天,還是OI Wiki講得靠譜。

優化

我們首先可以觀察到一點就是,i + 1位置的前綴函數pi[i + 1],最大也只能比pi[i]大1。

這個應該不難理解。因為相對於上一個子串,長度只增加了一,就算是最完美的情況下,前綴函數(PMT)的值也只能加1。例如字符串abab,前三個前綴函數是[0, 0, 1],現在看pi[3]也就是第四位,前綴只能增加為2。

但是這里難點就在於,我們要思考是什么情況可以使得p[i + 1] == p[i] + 1。好吧,我覺得一般人也思考不出來這個東西,答案就是s[i + 1] == s[pi[i]]的時候。

這個式子初看直接懵逼,卡了我一小時,突然想到舉個例子不就完了,於是舉個例子,確實很簡單。。。可惜我一個人自學,就是容易走進死胡同。

看例子saba,其pi[0,0,1]我們現在來填第四個字符,使得pi變為[0,0,1,2]。根據公式知道s[pi[2]] == 'b',變為abab

倒推下原因,因為pi[2] == 1其實就是說ab(a),右邊這個a處的最長公共前后綴長度為1,其實就是前后的兩個a。現在想讓pi[3] == p[2] + 1,就是使得新的最長前綴變為ab,那么其相應的后綴ax,就只能是ab,所以合並abax == abab

推廣

上面的優化方式其實不僅能用邊界情況的一次,其實可以一直往前推廣。我們第一次是看abax中的s[3]s[1]的對比(也就是s[3]s[pi[2]]),要是這個對比不相等,我們就繼續比較s[3]s[pi[pi[2]] - 1]。這么說好像更復雜了。。。直接看代碼吧:

//j是當前最大前綴長度
vector<int> pi(s.size());
for (int i = 1; i < s. size(); ++i) {
    int j = pi[i - 1];
    while (j > 0 && s[i] != s[j]) j = pi[j - 1];//一次不成,就把結論往前推廣
    if (s[i] == s[j]) ++j;//成了,依然是+1
    pi[i] = j;
}

循環隱藏

上面j每次都是從最邊界的情況開始往前迭代,其實還能偷懶,寫成這樣:

vector<int> pi(s.size());
for (int i = 1, j = 0; i < s.size() - 1;) //最后一位其實可以不算,因為沒用
{
    if (s[i] == s[j]) {
        pi[i] = ++j;
        ++i;
    }
    else {
        //把內層循環合並了
        if (j == 0) {
            ++i;
        }
        else {
            j = pi[j - 1];
        }
    }
}

復雜度分析

眾所周知KMP時間復雜度是O(N+M)。查找的過程O(N)先不說,我現在還比較困惑為什么構建前綴函數的過程只有O(M)次操作。j不是在回滾嗎?網上說用均攤分析,但是似乎都沒說清楚,有知道的人能說一下嗎?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM