學習UKK後綴樹構造算法

學習了UKK構建後綴樹的算法,挺不好理解。努力寫下自己的理解。

I 基本思想

構建後綴樹的基本思想:假設 T[0..i-1] 的後綴樹已經建好了,那麼在 T[0..i-1] 的每個後綴 T[0..i-1], T[1..i-1] .. T[j..i-1] .. T[i-1..i-1],””(空字符串),的後面加上字符 T[i] 就可以得到 T[0..i] 的後綴樹了。注意,有一個底節點“bottom vertex”和根連接,這個節點和根連接的邊對應空字符串。

若T[0...i-1]已經構建好,這個樹上的節點分爲三種:葉節點,顯式節點,隱式節點。從根到每個節點的所有邊上的字符組成一個字符串,我們就說每個節點對應一個後綴字符串,其中根節點對應的是空字符串;反過來就是每個後綴或者每個字串在樹上都對應若干條邊,但是一個後綴的結束節點可能是顯示節點或葉節點,也可能是隱式節點;

具體到 “將 T[i] 加到後綴T[j..i-1]” 的這個過程,則有可能遇到三種擴展情況:

擴展情況1.在後綴樹中,T[j..i-1] 停在了葉節點上。這種情況下,只需要把 T[i] 加到該葉節點的入邊上即可。

擴展情況2.在後綴樹中,T[j..i-1] 的後面緊跟着的字符的集合 {a,b,c ... } 中不包含 T[i]。兩種情況:1)T[j...i-1]在顯式節點結束,這個節點的每個子樹的邊對應的字符串都不以T[i]開始;2)T[j...i-1]在隱式節點結束,這個節點所在邊的下一個字符不是T[i]。這種情況下,在 T[j..i-1] 的後面加上樹葉 T[i](可能需要把隱式節點變換成顯式節點)。

擴展情況3.後綴樹中已經有 T[j..i] 了。這種情況下,我們什麼都不用做。

例如,構建BOOB的後綴樹,若BOO的後綴樹已經構建完,

                                                                                                                                            BOO後綴樹

BOO的後綴包括T[0..2]=BOO, T[1..2]=OO, T[2..2]=O, “”(空字符串)。

T[3]=“B”,依次將T[3]添加到BOO的每個後綴上:

i)T[3]添加到T[0..2],T[0..2]=“BOO“是在葉節點結束,按第一種情況添加。


ii) T[3]添加到T[1..2],T[1..2]=“OO”是在葉節點結束,按第一種情況添加。


iii) T[3]添加到T[2..2],T[2..2]=“O”是在隱式節點結束,這個隱式節點在根節點到2號節點的邊上,它後面的字符是”O”,不是”B”(見上圖),按第二種情況添加。這時要把隱式節點變爲顯式,然後添加一個葉節點。


iv) T[3]添加到空字符串。空字符串是在根節點結束,根節點是顯式節點,它有兩個邊,其中第一個邊的首字符是”B”,和T[3]一樣(見上圖),所以這種添加情況就是第三者情況。


添加完T[3]到每個後綴後,還沒有結束,這時如果在後綴樹上找後綴”B”,就不好找了。我們在字符串後面添加一個特殊字符,一般是”$”或者”#”,也就是BOOB$,我們在把這個特殊字符”$”添加到後綴樹上。

這樣,搜索後綴時,只要搜索到字符串以”$”結束,就表示搜索到了一個後綴。找後綴B,就是”root->5->6”。

 

II 葉節點的特性

 

後綴樹T[0..i-1]如果有n個葉節點,那麼這些葉節點必然是對應後綴T[0..i-1],T[1..i-1],…,T[n-1..i-1]。因爲如果T[j..i-1]不是在葉節點結束(是顯式節點或者隱式節點),那麼T[j+1..i-1]也不是在葉節點結束。如果T[j..i-1]不是在葉節點,假設T[j+1..i-1]=s,則T[j..i-1]=as, a是一個字符;那麼必然至少有as+s1(s1至少是一個字符)是一個後綴,那麼s+s1也是一個後綴,我們知道一個字符子串在後綴樹上的路徑(從根開始)有且只有一條,字串s+s1’的路徑,肯定是找到s後再繼續找s1’的路徑,如果s是葉節點,就不能繼續找到s1’了,也就是s+s1不是後綴,這顯然不正確。所以s不可能是葉節點,也就是T[j+1..i-1]不是葉節點。

 

如果T[j..i-1]是葉節點,添加的方法就是把T[i]加到邊上。在以後的擴充後綴樹的過程中,這個葉節點永遠是葉節點,只是可能會把邊上的某個隱式節點變爲顯式節點。因此我們在增加一個葉節點後,就讓這個葉節點的入邊的結束字符指向最後一個字符。舉例理解。我們要構建BOOKE的後綴樹,從構建的過程中我們發現每次新加入的葉節點(節點1,節點2,節點4,節點5,節點6),一直到整個樹構建完,他們都是葉節點,葉節點的特點就是入邊的最後一個字符一定指向整個字符串的最後一個字符,所以從一開始每個葉節點的入邊的最後一個字符就指向整個字符串的最後一個字符,每次擴充後綴樹時都不需要擴充葉節點的入邊了。

                          

             步驟1                                                        步驟2                                                     步驟3

          

                             步驟4                                                                          步驟5

 

III. 對非葉節點的擴充

 

既然我們擴充時不需要關心葉節點,我們就需要找到第一個非葉節點,從這個節點對應的後綴開始擴充。把第一個非葉節點稱爲激活節點。比如,T[0..i-1]已經構建,若有n個葉節點,那麼第一個非葉節點,也就是激活節點就是T[n..i-1]對應的節點,我們添加只要從激活節點開始。如果所有非空後綴都是葉節點,那麼根節點就是激活節點。添加時如果遇到上面所說的擴展情況3,則添加結束,我們把結束時的後綴對應的節點稱爲結束節點

 

結束情況1:如果擴展時一直是上面擴展情況2,那麼最後是將T[i]添加到根節點後,根節點是結束節點;

結束情況2:如果在添加T[i]到T[k…i-1]時遇到擴展情況3,那麼添加T[i]到T[0…i-1]各個後綴的過程就結束了,T[k…i-1]對應的節點就是結束節點。

 

分析上面兩種情況的結束節點,我們可以知道比結束節點對應後綴長的的後綴肯定都是在葉節點結束。假設我們添加T[i]到T[0..i-1], T[0..i-1]有n個葉節點,那麼添加從後綴T[n..i-1]開始,假設後綴T[k..i-1]對應的節點是結束節點。因爲葉節點添加之後還是葉節點,那麼新後綴T[0..i],T[1..i],…,T[n-1..i]一定在葉節點結束;新後綴T[n…i]到T[k-1….i]的結束節點一定是按照上面的添加情況2來添加的,而新添加的節點也一定是葉節點,所以新後綴T[n+1..i]到T[k-1..i]也都是在葉節點結束。T[k..i]就是添加T[i+1]到T[0..i]後綴樹上時的激活節點。

 

IV.尋找下一個更短後綴的方法

我們在生成後綴樹T[0..i]時,是需要找到每一個T[0..i-1]的不在葉節點結束的後綴,如果我們已經添加完T[p..i-1],我們需要尋找下一個後綴T[p+1..i-1]。所謂尋找T[p+1..i-1]就是找到T[p+1..i-1]後綴的結束節點,因爲我們最終是要把T[i]添加到後綴的結束節點上(這個結束節點是一個後綴的結束節點,任何一個後綴,從根節點開始,總是在一個節點結束。不是上面所說的某一輪添加過程中的結束節點),如果在顯式節點結束,就是這個顯式節點;如果在隱式節點結束,就是隱式節點所在邊的開始節點。

i尋找方法一:從根節點開始尋找

在尋找的過程中,我們需要運用skip/count的技巧。

假設我們要從根開始尋找後綴T[p..i-1],我們只有找到根節點下首字符爲T[p]的邊,假設爲E1,後綴T[p..i-1]肯定沿着這條邊往下尋找。假設邊E1對應字符串長度爲length(E1),如果length(T[p..i-1])<=length(E1),那麼後綴T[p..i-1]的結束節點就是邊E1上的隱式節點或者是E1的節點;如果length(T[p..i-1])>length(E1),假設E1的另一個節點是V1,接着在V1的出邊中尋找首字符是T[length(E1)]的邊,後綴T[p..i-1]肯定繼續沿着這條邊往下尋找。類似的方法一直找到後綴T[p..i-1]。

 

ii.尋找方法二:利用後綴鏈接

什麼是後綴鏈接?

通俗說就是從一個字符串的結束節點,指向比它短一個字符的字符串的節點。

如果aS是一個字符串,a是一個字符,S是一個子串(可以是空串),若aS在一個顯式節點結束(假設爲v1),則S子串也是在一個後綴節點結束(假設爲v2),則從v1指向v2的指針稱爲後綴鏈接。

 上面的描述中隱含一個事實:如果aS字符串在一個顯式節點結束,那麼S字符串也一定在一個顯式節點結束。理解這個事實。既然aS在一個顯式節點結束,那麼一定存在後綴aS+S1,以及aS+S2,其中S1和S2是字符串或者單個字符,並且它們不相同其不爲空,那麼一定存在後綴S+S1以及S+S2。從根節點出發我們找到字符串S對應的節點,這樣的路徑存在且唯一,如果這個節點是葉節點,那麼就不可能存在後綴S+S1或者S+S2;如果這個節點是隱式節點,我們搜索S+S1或者S+S2,都一定先搜索到S然後向下繼續搜索,S是隱式節點,S之後就不可能跟不同的字符來組成不同的字符串,也就是以S開頭的後綴只能有一個,而現在是兩個,所以不可能是隱式節點。所以我們可以知道S對應的節點一定是顯式的。

 從上面的理解我們可以知道:

創建後綴樹過程中,如果在第i階段,第j步擴展中,創建了一個對應字符串爲aS的顯式節點,那麼要麼存在一個對應字符串爲S的顯式節點;要麼在下一步(j+1)創建一個對應字符串爲S的顯式節點。aS對應的節點到S對應的節點就建立一個後綴鏈接。

因此任何一個顯式節點(除根節點)都有後綴鏈接,通過後綴鏈接我們就可以快速的尋找下一個更短的字符串。

 

Reference:

http://www.if-yu.info/2010/10/3/suffix-tree.html

http://blog.163.com/lazy_p/blog/static/13510721620108139476816/(三鮮翻譯文章)原文在這裏:http://marknelson.us/1996/08/01/suffix-trees/

 http://www.allisons.org/ll/AlgDS/Tree/Suffix/


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章