看過非常多的不靠譜suffix tree介紹后,本文是我在網上發現至今最好的一篇,通過三個規則講述了整棵后綴樹的構建過程,圖形結合,非常容易理解,並且本文尊重原作者Ukkonen的論文術語,清楚的講解了出現在suffix tree中的每一個概念,花時3個小時翻譯之,共勉,部分有修改和拋棄。
正文如下:
接下來我將通過一個簡單的字符串(不包含重復的字符)來試着分析Ukkonen算法,接着來講述完整的算法框架。
首先,一點簡單的事前描述
1. 我們構建的是一個簡單的類似搜索字典類(search trie)結構,所以存在一個根節點(root node)。樹的邊(edges)指向一個新的節點,直到葉節點。
2. 但是,不同於搜索字典類(search trie),邊標簽(edge label)不是單個字符,相反,每一個邊被標記為一對整數[from, to]。這一對整數是輸入字符串的索引(index)。這樣,每一個邊記錄了任意長度的子字符(substring),但是只需要O(1)空間復雜度(一對整數索引)。
基本約定
下面我將用一個沒有重復字符的字符串來說明如何創建一顆后綴樹(suffix tree):
abc
本算法將從字符串的左邊向右邊一步一步的執行。每一步處理輸入字符串的一個字符,並且每一步抑或涉及不止一種的操作,但是所有的操作數和是O(n)時間復雜度的。
好,我們現在將字符串左邊的a插入到后綴樹,並且將此邊標記為[0, #],它的意思是此邊代表了從索引0開始,在#索引結束的子字符串(我使用符號#表示當前結束索引,現在的值是1,恰好在a位置后面)。
所以,我們有初始化后的后綴樹:
其意思是:
現在我們處理索引2,字符b。我們每步的目的是將所有后綴(suffixes)的結束索引更新當前的索引。我們可以這樣做:
1. 拓展存在的a邊,使其成為ab;
2. 為b插入一條新邊。
然后變成這樣:
其意思是:
我們觀察到了二點:
- 表示ab的邊同我們初始化的后綴樹:[0, #]。它意味着將會自動改變,我們僅僅更新#,使其成為2即可;
- 每一步只需要O(1)的空間復雜度,因為我們只記錄了一對整數索引而已。
接下來,我們繼續自增#索引,現在我們需要插入字符c了。我們將c插入到后綴樹中的每一條邊,然后在為后綴c插入一條新邊。
它們像下面:
其意思是:
我們注意到:
- 在每一步后,恰好都是一顆正確的后綴樹;
- 總共需要字符串長度的數量的操作;
- 所有的操作都是O(1)。
第一次拓展:簡單的重復字符串
上面的算法工作的非常正確,接下來我們來看看更加復雜的字符串:
abcabxabcd
步驟1至3:正如之前的例子: