首先是後綴分類。我們會將所有後綴根據其起始位置 $i$ 對 3 的餘數進行分類,形成三種不同的類型:
- Type A ($i$%3=0):
這類後綴的起始位置是 3 的倍數。它們不會直接參與初始排序,而是透過 Type B 的排序結果間接排序。 - Type B1 ($i$%3=1):
這類後綴會與 Type B2 一起組成 Type B 後綴集合,並進行初步排序。 - Type B2 ($i$%3=2):
同樣屬於 Type B 後綴集合,與 B1 一起進行排序。
以 "mississkp" 為例,為了方便操作我們在其尾端加入兩個哨兵字元 '\0'。
- 對於每個 Type A 後綴 i,我們取:
- $S[i]$:當前字元
- $R[i+1]$:Type B1 後綴的排名(因為 i+1 % 3 = 1)
| 圖2 |
接著我們要合併兩種後綴的排序結果,可以使用 std::merge 合併,但須要寫一個 $\ord{1}$ 的比較函數。設比較函數 $cmp(x,y)$, $x$ 是某個 Type B 後綴的編號,$y$ 是某個 Type A 後綴的編號:
- $S[x]\ne S[y]$:
直接比較 $S[x],\;S[y]$ - Type B1 後綴 ($x$ % 3 = 1):
此時 $x+1$ 和 $y+1$ 都是 Type B 後綴,直接比較 $R[x+1],\;R[y+1]$ - Type B2 後綴 ($x$ % 3 = 2):
此時 $x+1$ 是 Type A 後綴, $y+1$ 是 Type B 後綴,直接呼叫 $!cmp(y+1,x+1)$
整體時間複雜度 $T(n)=T(2n/3)+\ord{n}=\ord{n}$,但要注意空間使用量
$$M(n) =
\begin{cases}
n + \delta(n) + 2, & n \leq 2 \\
n + \delta(n) + 2 + M\left(\left\lfloor \frac{2(n + \delta(n))}{3} \right\rfloor\right), & n > 2\end{cases}\\ \delta(n) = [n\equiv 1\left(\bmod 3\right)]$$
$$M(n) =
\begin{cases}
n + \delta(n) + 2, & n \leq 2 \\
n + \delta(n) + 2 + M\left(\left\lfloor \frac{2(n + \delta(n))}{3} \right\rfloor\right), & n > 2\end{cases}\\ \delta(n) = [n\equiv 1\left(\bmod 3\right)]$$
根據 AI 的推導 $M\left(n\right)\le 3n+\left\lfloor 4\log_{1.5}\left(n-2\right)\right\rfloor+1$。
| 圖3 |
2025/10/10 更新:有人發現我原始的程式碼在當 n%3=1 遞迴時在 Type B1 和 Type B2 之間沒有隔離符號,有可能導致這兩類後綴排序錯誤,聽說我一開始的參考資料 ([2009] 后缀数组——处理字符串的有力工具 by. 罗穗骞) 也有一樣的問題,因此我花了點時間重寫了 DC3 的教學。
錯誤原因如圖4 所示,若不加上這個哨兵字元的話,後綴 7 就不會被加入遞迴運算,這樣會導致某個 Type B1 後綴將不是自己正確後綴的 Type B2 後綴連接起來排序導致結果錯誤。
以下提供模板(註解是請 AI 寫的,拿掉之後程式碼應該會很短):
沒有留言:
張貼留言