Z-function

String Processing

18/04/2019

Cho 2 xâu P (pattern) và S (string). Mộ số bài toán

  • Tìm vị trí xuất hiện đầu tiên của P trong S
  • Đếm số lần xuất hiện P trong S
  • Cho S, tìm P ngắn nhất để repeat P nhiều lần ta được S

I. Z-function

1. Định nghĩa Z-function

Cho xâu $S = s[0..n-1]$.
Hàm $Z$ của xâu $S$ là dãy $z[0], z[1], …, z[n-1]$ trong đó $z[i]$ là max (length) prefix của $s[i..n-1]$ với $s[0..n-1]$.
Ví dụ: Xét xâu $S = ‘abaabab'$ (length = 7)
Ta sẽ có được hàm z

i 2 xâu cần so sánh z[i]
0 quy ước = 0 0
1 ‘abaabab’ vs ‘baabab’ 0
2 ‘abaabab’ vs ‘aabab’ 1
3 ‘abaabab’ vs ‘abab’ 3
4 ‘abaabab’ vs ‘bab’ 0
5 ‘abaabab’ vs ‘ab’ 2
6 ‘abaabab’ vs ‘b’ 0

Sau khi có được hàm Z của xâu S, thì với $z[k] = x$ cho ta biết:

  • $x$ kí tự đầu của $S$ sẽ xuất hiện lại ở vị trí $k$
  • Số phần tử có giá trị $x$ là số lần xuất hiện của $S[0..x-1]$ trong $S$.

2. Implement

a) Thuật toán bình thường

1z[i] = 0 for all i
2for(int i=1; i<n; i++){
3    while(i+z[i] < n && s[z[i]] == s[i+z[i]]) 
4        z[i]++;
5}

$ \Rightarrow O(n^2)$

b) Thuật toán cải tiến

Ý tưởng: Ta sẽ bám vào thằng xa nhất mà ta đã nhìn thấy.
Như thế nào là thằng xa nhất ta đã nhìn thấy?

Ví dụ: ta đang tính $z[i]$ được $k$ thì thằng xa nhất mà ta có thể nhìn thấy là $i+k-1$.
Gọi thằng xa nhất mà ta có thể nhìn thấy là $right\_most$ thì
$$ right\_most = max(j+z[j]-1) \hspace{9mm} \forall 0 \leq j \leq current\ i $$ Từ thằng $right\_most$ ta sẽ xác định được điểm xuất phát của nó là $left$ (ta có lưu lại mà). Nghĩa là mình bám đoạn $left$ đến $right\_most$ (length = k). (điều chắc chắn là $i > left$).

Có phải là đoạn $s[i..right\_most]$ nó hoàn toàn giống với đoạn $s[i-left..k-1]$ vì $z[left] = k$. Vì vậy ta sẽ tận dụng việc đoạn này đã được so sánh prefix với $s$ trước đó thông qua việc đi tính $z[i-left]$ trước đó.

Đọc code sẽ rõ, nếu may mắn full cây (trùng cả đoạn) thì tăng lên so sánh tiếp, không thì được 1 khúc trùng với $z[i-left]$.

 1vector<int> z_func(string s){
 2    int n = s.length();
 3    vector<int> z(n);
 4    int l = 0, r = 0;
 5    for(int i=1; i<n; i++){
 6        z[i] = max(0, min(z[i-l], r-i+1));
 7        while(i+z[i] < n && s[z[i]] == s[i+z[i]]) {
 8            z[i]++;
 9        }
10        if (i+z[i]-1 > r) {
11            l = i;
12            r = i+z[i]-1;
13        }
14    }
15    return z;
16}

$ \Rightarrow O(n)$
I think this is good link for you to discover Z-function: https://cp-algorithms.com/string/z-function.html