← 回首頁

後綴陣列與持久化線段樹(Suffix Array / Persistent Segment Tree)

後綴陣列把一段文字的「所有結尾片段」排好序,之後要在這段文字裡快速搜尋、找最長重複片段都很方便(搜尋引擎、DNA 序列比對常用)。 持久化線段樹則是「會保留每個歷史版本」的資料結構——每次修改不覆蓋舊資料,而是只新增少少幾個節點、其餘和舊版本共用, 所以能隨時查任何一個過去版本(用途:版本控制的撤銷/重做、以及「求某區間第 k 小的數」這類經典問題)。

在一大段文字裡想快速找出某個詞出現在哪、或哪一段重複最多次,一個字一個字比對太慢;而想反悔剛才的修改、 回到幾步前的樣子,把整份資料整包複製存檔又太佔空間。這頁的兩個結構正好各解一題:後綴陣列先把文字裡 所有結尾片段排好序,像書末的索引一樣,讓你用二分搜尋快速定位任何字;持久化線段樹則像文件的版本紀錄, 每次只多存被改到的那幾個節點、其餘和舊版共用,所以每個過去版本都留得住、又不佔空間。下面你會看到它們各自怎麼運作。

兩者都是「先把資料整理好、再用結構省下重複工作」的代表:後綴陣列把一段文字的所有後綴排序成表, 搭配 LCP(最長共同前綴)陣列與二分搜尋,把模式字串搜尋壓到 O(m log n);持久化線段樹則是一般線段樹的 「路徑複製」延伸——每次更新不覆蓋舊資料,而是沿更新路徑新建 O(log n) 個節點、其餘子樹與舊版本直接共享 參照,換取「所有歷史版本皆可查詢」且空間增量只有 O(log n)。

1. 後綴陣列(Suffix Array,倍增法建構 + Kasai LCP + 二分搜尋)

倍增法(doubling):初始 rank[i]=首字元碼、sa[i]=i,每輪 k=1,2,4,... 依 (rank[i], rank[i+k]) 二元組重新排序 並指派新名次,二元組全相異即提前結束(本頁 complexity 依 Java 建構法誠實標 O(n log²n))。排序完成後, Kasai 演算法依原始位置序、利用 h 只減不歸零的攤提技巧,以 O(n) 求出每個排名的 LCP(與前一名後綴的最長共同前綴)。 最後對固定 pattern 做兩次二分搜尋:先找「第一個 >= pattern」的下界,再找「第一個 > pattern」的上界, [下界,上界) 區間內即所有匹配位置。

固定資料:text="banana"、pattern="ana"。獨立樸素驗證:sa=[5,3,1,0,4,2](後綴依序 a, ana, anana, banana, na, nana); pattern "ana" 匹配位置(依 sa 收集序,即二分搜尋在排序表中由上而下依序收進 matches 的順序)=[3,1](排序後即 [1,3])。
比較次數:0
排序表共 n 列 × 4 欄:欄0=名次 rank(依最終排序後的第幾名)、欄1=sa 起點(該名次對應的後綴起始索引)、 欄2=後綴字串、欄3=LCP(與前一名後綴的最長共同前綴長度);藍色外框(active)=當前比較/揭露的列, 粉紅(pivot)=二分搜尋當前 mid 所在格。搜尋階段的 lo/hi 收斂範圍與匹配結果見右側面板。

二分搜尋範圍

lo
hi

匹配位置(matches,依 sa 收集序)

已執行操作紀錄(opLog)

2. 持久化線段樹(Persistent Segment Tree,路徑複製 + 多版本查詢)

update(version, index, value) 從指定版本的根出發,沿更新路徑複製節點(newNode = node.copy()): index <= mid 複製並遞迴左子、否則複製並遞迴右子,另一側子樹不修改,直接共享舊節點參照(不複製); 抵達葉節點直接賦新值,回溯時重算 sum,新版本的根收進 versions[]。query(version, l, r) 則從該版本的根 出發做標準線段樹查詢:完全包含回傳 sum、完全不相交回傳 0、部分重疊遞迴左右子相加。

固定資料:base=[1,2,3,4](版本 0)。獨立版本陣列驗證:update(0,idx2,7)→v1=[1,2,7,4]; update(1,idx0,5)→v2=[5,2,7,4];query v0[0,3]=10、v1[0,3]=14、v2[0,3]=18、v2[1,2]=9。

操作腳本

    累計新建節點數:0 版本數:0
    共享節點只畫一份:graph 由目前為止建立過的全部節點重新計算,舊版本節點若未被路徑複製覆蓋, 會以同一個 id、同一份內容持續出現在後續所有幀中——這正是持久化資料結構「不重複拷貝未修改子樹」的直接證據。 綠色高亮(finalized)= 本次 update 沿路徑新建的節點,是路徑複製的教學核心:一次 update 只新增 O(log n) 個綠色節點,其餘節點原 id 直接沿用舊版本、不重建。藍色粗外框(visited)= 本次操作走訪過的節點: update 時僅為路徑複製新建的節點(與綠色 finalized 同一集合,不含舊共享節點);query 時則為遞迴走訪過的 全部節點(含未修改、直接沿用舊版本的共享節點)。橘色(current)= 當前正在處理的節點;節點上方文字 (v0/v1/v2)僅標示版本根。

    查詢結果(queryResult)

    本次新建節點(newNodes)

    已執行操作紀錄(opLog)