1. 程式人生 > >KMP演算法、next陣列與字首中的週期(相關題目:Power strings, poj2406)

KMP演算法、next陣列與字首中的週期(相關題目:Power strings, poj2406)

在一個大的字串S中查詢字串T,naive的演算法時間複雜度為O(s * t)(這裡s與t代表S的長度與T的長度);而應用KMP,時間複雜度為O(s + t)。 KMP演算法的核心在於next陣列。next陣列只與字串T有關,與S無關。 next陣列的核心思想是儲存字串T的內容的相似性資訊,具體而言: next陣列記錄了T的每一個字首子串M(m>=2)中,(M的)相等的字首真子串與字尾真子串的最大長度。換句話說,就是M的首部和M的尾部最多有多少個字母完全相同。 舉個例子,若T = agctagcagctagctg,求其next陣列。
例如,字首子串M = agctagcagct,長度為11,首部的agct與尾部的agct是一樣的,長度為4,所以next[11 -1] = next[10] = 4。 那麼,如何程式設計求字串T的next陣列呢?Here is a bit tricky. 舉個例子,T的字首子串M = (a)(b)(a)...(a)(b)(a)(X),我們已經計算出: next0 = 0 next1 = 0 next2 = 1 next3 = 1 next4 = 2 next5 = 3 現在要計算next6即字元X的next陣列值。 先看X的前一個字元的next陣列值next5,為3,說明X前面的3個字元和開頭的3個字元已經相同了: 如果X與第4個字元相同,即X == a,於是M的前4個字元與後4個字元相同,所以next5 = 4; 如果X與第4個字元不相同,比如X == b,於是需要找短一點的相同字串,長度小於next5 = 3的。於是下一步我們考察next[next[5]],也就是next[3]的值,next3=1。這裡面的邏輯是
:看子串M,因為next3=1,所以1號a與2號a相同,又因為next5=3,所以aba與aba相同,這意味著1號a與3號a相同,2號a與4號a相同,綜上,1號a與4號a相同。所以,只要比較X(4號a的下一個字元)與1號a的下一個字元是否相同即可。這裡X == b,與M的第二個字元相同,所以next6 = 2;就這樣一直遞迴查詢下去; 如果以上情況都不符合,next6 = 0。 下面是程式碼:
void get_next(const string T, int next[])
{
    int len = strlen(T);
    int i, k;    

    next[0] = 0;    //顯然,next陣列的第一個元素總是0

    for (i=1; i<len; ++i)
    {
        k = next[i-1];    //取得前一個字元的next值
        while (T[i] != T[k] && k != 0) k = next[k-1];    //如果不等,繼續遞迴
        
        if (T[i] == T[k]) next[i] = k+1;    //直接延續前一個字元的對稱性,或者找到稍小一些的對稱性
        else next[i] = 0;
    }
}

接下來,考察next陣列的一個性質: 字串T的長度為len,若len % (len - next[len-1]) == 0(其中next[len-1] != 0),則T有長度為(len - next[len-1])的迴圈節(長度最小的重複單位)。 例如,若T = aabaabaabaab,next[11] = 9,12 % (12 - 9) == 0,則T有長度為3的迴圈節。腦補一下以aab為單位的子串比較過程即可理解。用這個性質可解決poj 2406。