Z 演算法
可以線性時間在一段文本(Text) 裡面找到所有我們欲求的段落(Pattern)
- 文本(Text)的長度為 n
- 段落(Pattern)為 m
搜尋過程需要線性等級的時間複雜度:O(m+n)
雖然這個演算法需要的空間(Space complexity)與時間複雜度(Time complexity)都與KMP algorithm一致,但是這個演算法比起「KMP algorithm」還要容易了解
KMP algorithm:每個前綴與其後綴的次長共同前綴(最長的後綴) Z algorithm:每個後綴與母字串的最長共同前綴(單純的長度)
首先,我們需要一個 Z陣列(Z array)
Z 陣列
當我們將欲檢索的文本存為一個字串 str[0n−1] 時,同時也建立一個與字串一樣長的Z陣列。 在Z陣列中,第 i 元素紀錄「最長共同前總和 (Longest Common Prefix)的長度」,而 LCP 的長度 是由「從 i 開始的後總和 (Postfix)」與「該文本」共同決定。 ( 注意: Z[0] 毫無意義可言,因為從第0個開始的後總和(Postfix) 必與原本的文本字串相同。 )
大致上我們可以看成如下的函式:

Ex.
1 | Index 0 1 2 3 4 5 6 7 8 9 10 11 |
1 | str = "aaaaaa" |
Z 陣列如何幫助演算法加速?
這個演算法的想法是將段落(pattern)與文本字串(text string)連接起來,若視段落(pattern)為「P」,視文本字串(text string)為「T」,並加上一個從未在段落與文本中出現過的字元「$」再產生出如「P$T」的字串。
最後,我們再產生一個屬於「P$T」的 Z陣列,在 Z陣列之中,若該 Z值等於段落(pattern)的長度,段落出現在該處。
1 | Example: |
如何建立 Z陣列
最簡單的就是使用兩個迴圈,外層迴圈將整個「P$T」跑過一遍,內層迴圈則是看看到底 i 位置的後總和與「P$T」的LCP長度為何。 Time complexity: O(n2)
我們當然可以使用另一種方法讓建立陣列的時間複雜度降低。 此演算法的關鍵在於要維護一個區間[L…R],R 的位置代表由 L 處之後可以和整個字串最長的前總和重疊到的最後一個位置( 換句話說:[L…R]是整個字串的前綴子字串 ),若完全不重疊,則 L 與 R相等。
- 步驟 (i 為當前位置)
- 若 i>R ,就代表當前 i 沒有經過任何「P$S」的前綴子字串,所以重置 L 與 R 的位置(L=i,R=i),經由比對「P$S」的前綴與 i 之後的前綴,並找出最長的子字串(R 的位置),計算新的 L 與 R 的位置,也一併將 Z[i]值算出來(=R−L+1)。
- 若 i≤R ,令 K=i−L ,再來我們知道 Z[i]≥min(Z[K],R−i+1) 因為String[i…]與String[K…]共同前R−i+1個字元必然為[P$T]的前綴子字串。現在有兩種情形會發生:
- case1: 若Z[K]<R−i+1 ,代表沒有任何「P$S」的前綴子字串 從 i 位置開始(否則 Z[K] 的值會更大),所以也意味著Z[i]=Z[K],還有區間[L…R]不變。
- case2: 若Z[K]≥R−i+1,代表String[i…]可以和String[0…] 繼續比對相同的字元,也就意味有可能拓展[L…R] 區間,因此,我們會設 L=i ,接著從 R 之後開始繼續比對「P$S」的前綴子字串,最後我們會得到新的R,並更新[L…R] 區間與計算 Z[i] (=R−L+1)。
想要了解上述的演算法可以經由這個連結觀看動畫。
小視窗

如果一個位置 i 位於之前比過的那段 [L,R] 當中,他是否跟 Z[i−L] 相同呢?我們可以分成三種情形: 1. 要比的後綴根本不在以前比過的範圍[L,R]內 → 就去比吧! 2. 要比的後綴在以前比過的範圍[L,R]但長度未知 → 還是去比吧! 3. 要比的後綴在以前比過的範圍[L,R]但長度已知 → 直接記錄囉!
程式碼實作
台大資工PPT by nkng
1 | void z_build(const char *S, int *Z) { |
Z algorithm - GeeksforGeeks
1 | // A C++ program that implements Z algorithm for pattern searching |
建國中學 2012 年資訊能力競賽培訓講義 - 08
1 | void Z_maker( int z[], char s[], int n ){ |
Z algorithm - codeforces
1 | int L = 0, R = 0; |
Z algorithm1 - 日月卦長的模板庫
1 | inline void z_alg1(char *s,int len,int *z){ |
Z algorithm2 - 日月卦長的模板庫
1 | inline void z_alg2(char *s,int len,int *z){ |
培訓-4 字串- tioj
1 | void z_build(const char* S,int *z){ |
例題
TIOJ 1725_Z algorithm_Massacre at Camp Happy
參考
Gusfield algorithm - momo funny codes
待補充
KMP 字串比對演算法
http://mropengate.blogspot.tw/2016/01/leetcode-kmpimplement-strstr.html