---
# System prepended metadata

title: O(N) Suffix Array

---

# O(N) Suffix Array

**Người viết:**
> **Nguyễn Tường Duy - VNU University of Engineering and Technology**

## Bài toán

Cho xâu $S$ độ dài $n$ thỏa mãn $S[i] \leq n$ ($1 \leq i \leq n$), sắp xếp các hậu tố của $S$ theo thứ tự từ điển (hậu tố của $S$ được biểu diễn bằng vị trí $i$ tương ứng với $S[i, n]$), gọi mảng đã sắp xếp đó là mảng hậu tố.

## Skew Algorithm

Ý tưởng của thuật toán là:

**Bước 1.** Sắp xếp các hậu tố ở vị trí $i$ mod $3 \neq 0$.
**Bước 2.** Sắp xếp các hậu tố ở vị trí $i$ mod $3 = 0$ sử dụng kết quả của bước 1.
**Bước 3.** Gộp mảng hậu tố ở bước $1$ và $2$ bằng thuật toán merge sort.

### Bước 1

Đầu tiên ta sắp xếp các hậu tố ở vị trí $i$ mod $3 \neq 0$ theo giá trị của hậu tố $i$ là $S[i, i + 2]$. Ta sẽ dùng thuật toán counting sort để sắp xếp trong thời gian $O(n)$.

Nếu các giá trị của hậu tố phân biệt thì ta đã hoàn thành bước $1$. Nếu không thì ta sẽ sắp xếp các hậu tố này theo cách đệ quy.

Cụ thể hơn ta có mảng $S_{12} = [1, 4, 7, \dots, 2, 5, 8, \dots]$, ta sẽ sắp xếp $S_{12}$ theo các giá trị sau: $[S[1, 3], S[4, 6], S[7, 9], \dots, -\infty, S[2, 4], S[5, 7], S[8, 10], \dots]$ (thêm giá trị $-\infty$ vào giữa để ngăn cách hậu tố $i$ mod $3 = 1$ với $i$ mod $3 = 2$).
Sau khi sắp xếp xong ta sẽ có mảng $e$ là giá trị tương đương của các hậu tố ($e$ thỏa mãn hậu tố $i <$ hậu tố $j$ thì $e[i] < e[j]$, hậu tố $i =$ hậu tố $j$ thì $e[i] = e[j]$). Ta sẽ tính đệ quy mảng hậu tố của mảng $[e[S_{12}[1]], e[S_{12}[2]], \dots]$. Ta thấy hậu tố của mảng $e$ tương đương với các hậu tố trong mảng $S_{12}$.
(ví dụ hậu tố $1$ là $[S[1], S[2], S[3], S[4], S[5], S[6], \dots] = [e[S[1, 3]], e[S[4, 6]], \dots]$)

### Bước 2

Ta sẽ sắp xếp các hậu tố ở vị trí $i$ mod $3 = 0$ theo giá trị của hậu tố $i$ là $S[i]$ và thứ tự của hậu tố $i + 1$ trong mảng hậu tố ở bước $1$. Ta dùng thuật toán counting sort để sắp xếp trong thời gian $O(n)$.

### Bước 3

Để gộp hai mảng hậu tố bằng thuật toán merge sort thì ta cần so sánh hậu tố $i$ mod $3 = 0$ với hậu tố $j$ mod $3 \neq 0$. Ta sẽ so sánh hai hậu tố trong thời gian $O(1)$ như sau:

* So sánh hậu tố $i$ mod $3 = 0$ và $j$ mod $3 = 1$ bằng cách so sánh $S[i]$ và $S[j]$, vị trí của hậu tố $i + 1$ và hậu tố $j + 1$ trong mảng hậu tố ở bước $1$.
* So sánh hậu tố $i$ mod $3 = 0$ và $j$ mod $3 = 2$ bằng cách so sánh $S[i]$ và $S[j]$, so sánh $S[i + 1]$ và $S[j + 1]$, vị trí của hậu tố $i + 2$ và hậu tố $j + 2$ trong mảng hậu tố ở bước $1$.

## Tổng kết

Ta sẽ phân tích độ phức tạp thời gian ở từng bước:
* **Bước 1:** $O(n + \frac{2}{3} \cdot n + \frac{4}{9} \cdot n + \dots) = O(n)$
* **Bước 2:** $O(n)$
* **Bước 3:** $O(n)$

Như vậy ta được thuật toán có độ phức tạp thời gian $O(n)$.

## Cài đặt

Những lưu ý khi code:

* $n = 1$ thì mảng hậu tố là $[0]$.
* Ta sẽ thêm $2$ phần tử $0$ vào cuối xâu $S$ để code dễ hơn.
* Khi sắp xếp đệ quy mảng hậu tố ở bước $1$ thì ta sẽ thêm giá trị tương ứng với $-\infty$ giữa hậu tố $i$ mod $3 = 1$ với $i$ mod $3 = 2$.

Code:
``` cpp=
void counting_sort(vector<int> &arr, const vector<int>::iterator &val) {
    int ma = 0;
    for (int &x: arr) ma = max(ma, val[x]);
    vector<int> cnt(ma + 1);
    for (int &x: arr) ++cnt[val[x]];
    for (int i = 0, sum = 0; i <= ma; ++i) {
        int tmp = cnt[i];
        cnt[i] = sum;
        sum += tmp;
    }
    vector<int> new_arr(arr.size());
    for (int &x: arr) new_arr[cnt[val[x]]++] = x;
    arr = new_arr;
}
vector<int> suffix_array(vector<int> s) {
    int n = s.size();
    if (n == 1) return vector<int>(1);
    s.resize(n + 2);
    int n0 = (n + 2) / 3, n1 = (n + 1) / 3, n2 = n / 3, n12 = n1 + n2;
    vector<int> sa12(n12);
    for (int i = 0; i < n1; ++i) sa12[i] = i * 3 + 1;
    for (int i = 0; i < n2; ++i) sa12[i + n1] = i * 3 + 2;
    counting_sort(sa12, s.begin() + 2);
    counting_sort(sa12, s.begin() + 1);
    counting_sort(sa12, s.begin());
    vector<int> equiv(n + 1);
    equiv[sa12[0]] = 2;
    for (int i = 1; i < n12; ++i) equiv[sa12[i]] = equiv[sa12[i - 1]] + (s[sa12[i]] != s[sa12[i - 1]] ||
                                                                         s[sa12[i] + 1] != s[sa12[i - 1] + 1] ||
                                                                         s[sa12[i] + 2] != s[sa12[i - 1] + 2]);
    if (equiv[sa12[n12 - 1]] <= n12) {
        vector<int> s12(n12 + 1);
        for (int i = 0; i < n1; ++i) s12[i] = equiv[i * 3 + 1];
        s12[n1] = 1;
        for (int i = 0; i < n2; ++i) s12[i + n1 + 1] = equiv[i * 3 + 2];
        sa12 = suffix_array(s12);
        sa12.erase(sa12.begin());
        for (int &x: sa12) x = x < n1 ? x * 3 + 1 : (x - n1) * 3 - 1;
        equiv[sa12[0]] = 2;
        for (int i = 1; i < n12; ++i) equiv[sa12[i]] = equiv[sa12[i - 1]] + 1;
    }
    vector<int> sa0(n0);
    for (int i = 0; i < n0; ++i) sa0[i] = i * 3;
    counting_sort(sa0, equiv.begin() + 1);
    counting_sort(sa0, s.begin());
    vector<int> sa(n);
    int i = 0, j = 0, k = 0;
    while (i < n12 && j < n0) {
        int suff12 = sa12[i], suff0 = sa0[j];
        while (true) {
            if (s[suff12] != s[suff0]) {
                sa[k++] = s[suff12] < s[suff0] ? sa12[i++] : sa0[j++];
                break;
            }
            ++suff12, ++suff0;
            if (equiv[suff12] && equiv[suff0]) {
                sa[k++] = equiv[suff12] < equiv[suff0] ? sa12[i++] : sa0[j++];
                break;
            }
        }
    }
    while (i < n12) sa[k++] = sa12[i++];
    while (j < n0) sa[k++] = sa0[j++];
    return sa;
}
```

## Tham khảo

https://www.cs.helsinki.fi/u/tpkarkka/publications/icalp03.pdf
