1. 程式人生 > >後綴數組及其建圖

後綴數組及其建圖

特定 結束 難點 後綴自動機 只需要 能夠 而不是 tps 新節點

目錄

  • 參考資料
  • 概念
  • 引理
  • 到底如何構造
    • Step1:
    • Step2:
    • Step3

第二次學了...感覺像是重頭來過一樣...

參考資料

後綴自動機學習筆記-by Menci
校內PPT

概念

  1. 後綴自動機實際上是兩個東西的合成物:一個有向無環圖(轉移)+一個後綴鏈接樹。並且後綴自動機最初始擁有一個根,暫且讓我們把它稱作Start

  2. 終點位置(endpos),就是一個字符串的右端點,即結束位置;

  3. 終點集合:母串中的一個子串肯定有可能在母串中不止出現了一次,例如ababab中的ab就出現了3次,那麽每一次出現的終點位置組成的集合就是這個子串的終點集合。

  4. 終點等價類:擁有相同的終點集合的子串就被稱作終點等價類。例如abcde,bcde,cde,de,e,它們在abcdeabcde這個母串中就屬於同一個終點等價類。

  5. 後綴自動機的第一部分(有向無環圖),邊代表字母(叫做轉移邊),點代表一個終點等價類(而不是一個某一個特定的串);第二部分(後綴鏈接樹),邊代表後綴鏈接,點的定義不變。兩個部分共享同一個點集。

  6. 在一個終點等價類(一個節點)裏面,我們還有兩個值,一個叫做minlen,是這個等價類代表的最小的子串;還有一個叫maxlen,是這個等價類代表的最大的子串。

  7. 後綴鏈接:後綴自動機中的每一個點(除了Start之外),都有這樣子的一個鏈接,其含義是將一個終點等價類分成很多個部分。假設當前的一個子串是abcde,有一個點是p,可能p只代表了abcde,bcde,而p的後綴鏈接指向的點pre[p](等價類)代表的可能就是cde,de,再往上還有一個點pre[pre[p]]代表著e,但是p只會連向pre[p],也就是maxlen[pre[p]]=minlen[p]-1

    的那個點。註意,Start代表著空串。也是所有串的後綴,屬於任意一個終點等價類。

(如有疏忽,後面會做詳細的解釋)

引理

一. 對於兩個非空子串str1,str2而言,如果strlen(str1)<=strlen(str2)並且str1和str2處於同一個終點等價類,那麽str1必定是str2的後綴。

證明:因為兩個串屬於同一個終點等價類,那麽它們右端點在母串中的任意一個位置一定是重合的。所以說str1一定是str2的後綴(幾乎可以由定義直接得到)。

二. 對於兩個非空子串str1,str2,如果滿足strlen(str1)<=strlen(str2),他們所代表的的終點集合要麽交集為空,要麽Set(str2)屬於Set(str1)。

證明:分兩種情況討論。
一種是str1是str2的後綴,那麽出現了str2的地方就一定出現了str1,但是出現了str1的地方不一定出現了str2,那麽根據終點集合的定義就能發現Set(str2) 屬於Set(str1);
還有一種情況,就是str1不是str2的後綴,那麽出現了str1的地方不可能出現str2,那麽交集就為空了。

三.在一個終點等價類裏面,將其代表的串排序後,一定呈現:前一個串時候一個串的後綴,並且前一個串的長度正好是後一個串的長度-1。

證明:假設有一個終點等價類,裏面最長的串是abcde,那麽這個等價類裏面必定還有bcde,cde,de,e,正好符合(又是由定義直接可得??)。

四.後綴鏈接形成了一棵以Start為根的一棵樹

證明:假設有一個節點p,因為它的後綴鏈接只會連向比maxlen自己的minlen小1的那個點,那麽就有了唯一的一條後綴鏈接。這樣每一個點都有唯一的後綴鏈接,而Start沒有後綴鏈接,那肯定就是一棵樹。

五:沿著後綴鏈接向上跑的過程,可以看做是將終點等價類進行不斷地合並,越往上走,能夠代表的終點等價類越完整

證明:由引理三的證明可以感受一下,越往下面的點,它代表的終點等價類的部分的子串數越來越少;而沿著後綴鏈接向上走的過程中,由於長度越來越短,所以說它能夠代表的後綴等價類也越來越多。

舉個例子:母串為abcdeabcde,其中代表eabcde那個點只有這一個子串,也只能夠代表這一個子串;而包含e能夠代表e,de,cde,bcde,abcde,eabcde...,能夠代表的終點等價類的範圍顯然就寬了不少,也就是將下面的那些子串合並起來了。

到底如何構造

說道底,這才是後綴自動機真正的難點所在...我甚至建議一邊看建圖過程一邊看引理的證明。過程主要註重理解。

在這裏采用的是動態的加入過程,也就是一個字符一個字符地加入。

假設當前加入的字符是chr,然後讓我們再來定義一些奇奇怪怪的變量:

  • 當前新建的點是np。
  • 上一次加入的點是p,也就是這次我們需要把np接在後面的那個點。同時p也是我們通過後綴鏈接不斷地向前跳的當前點。
  • 假設遇到p有一條字符chr的轉移邊,那麽就令q為p->nxt[chr]。
  • 如果要新建關於q的新建點的話,我們就叫它nq吧。

有了這些之後,讓我們來看一下怎樣建立後綴自動機。註意,後綴自動機的兩部分都要兼顧哦。(後面的圖中,虛邊都表示轉移邊,實邊都表示後綴鏈接)

Step1:

當前要加入的字符為chr。首先申請一個新的節點np,然後利用全局維護的上一次插入的新節點last,令p=last(根據之前的定義),然後又因為你新加入這個節點代表的最長的子串(也就是maxlen)就等於last的maxlen+1,即令maxlen[np]=maxlen[last+1](附加一句,其實在過程中我們只需要維護maxlen,minlen只是用來幫助我們進行分析的)。

Step2:

然後現在我們就有了新的節點np和當前位置p。我們沿著p的後綴鏈接往前跳,每走到一個點p,就需要判斷當前的p是否有字符chr這條轉移邊。如果沒有,就進行以下的操作:

在p點新添加一條chr的轉移邊,連向np。因為p點沒有一條到達chr的轉移邊,那麽在p處添加一個字符後可能就出現了以前沒有的串,所以需要新加入一條轉移邊。
新加入轉移邊後,就可以讓p繼續沿著後綴鏈接往前跑,每次繼續進行這樣的判斷就可以了。
(因為當前的p可能只代表了某個終點等價類的某一個長度區間,而為了能夠表示所有的子串,我們需要將以chr結尾的這個終點等價類的所有的子串都表示出來,多以需要往前跑)。

需要註意的一點是,假如說一直都沒有遇到一個點有chr這條轉移邊,那麽我們就需要將np的後綴鏈接指向Start。因為這個時候有maxlen[Start]=minlen[Start]-1,根據之前後綴鏈接的定義可得,此時必須,也只能將np的後綴鏈接指向Start。
技術分享圖片
如上圖,就是這樣跑(一直都沒有chr這條轉移邊時)建出來的圖的樣子了。

Step3

到了這裏,終於來到後綴自動機真正有意思的地方了,也是最復雜的地方。
我們前面說了沒有chr這條轉移邊的情況應該如何處理,那有chr這條轉移邊又該怎樣呢?

先上個圖:
技術分享圖片
當前的p點就是這樣一種情況(其中的chr邊是本來就有的)。q是p本來的chr邊轉移到的點。

然後我們需要分兩種情況進行討論。
其一是maxlen[p]+1=maxlen[q]。這個時候說明了q所代表的的最長的子串是由p加上chr之後轉移過來的,這個時候就可以直接讓np的後綴鏈接接到q上,然後直接退出就可以了(因為此時的q應該已經代表了它應該代表的完整的終點等價類)。也可以由公式算出:
\(maxlen[q]=maxlen[p]+1\)。設p之前在的那個點是p‘,又因為\(minlen[p‘]=minlen[u]-1\)\(maxlen[p]=minlen[p‘]-1\)。然後通過公式的代換,就能夠得到\(maxlen[q]=minlen[p‘]-1+1=minlen[u]-1\),再根據後綴鏈接的定義就應該連這條邊作為後綴鏈接了。

其二是maxlen[p]+1<maxlen[q],這個時候就不能夠直接讓np的後綴鏈接直接接到q上了,因為q此時不一定只代表了maxlen[p]+1的部分,還代表了>maxlen[p]+1的部分,而這一部分是不能夠被算進去的。這個時候應當怎麽辦呢?
此時我們需要新建一個節點,將q中>maxlen[p]+1的那些子串分離出來。
技術分享圖片

可以看出新加了不少的邊。
讓我們先從實邊,也就是後綴鏈接說起。因為nq代表的是maxlen[nq]=maxlen[p]+1的部分,而q代表的是maxlen[q]>maxlen[p]+1的部分,而minlen[q]=maxlen[nq]+1,所以說q必定要將後綴鏈接指向nq。而np的後綴鏈接必定也要連向nq。同時,由於可以將nq和q看做是一個整體,所以說nq應繼承q原來的信息,將後綴鏈接之前原來的q的後綴鏈接指向的那個點。

在來看轉移邊。p的後綴鏈接以上的點如果有連向q的轉移邊,也要重定向到nq。唯一不同的是t,是之前令maxlen[t]+1=maxlen[q]>maxlen[p]+1的那個點,它仍然需要將轉移邊指向q,因為此時上式仍然成立。而p的轉移邊應指向nq,因為此時我們設的就是maxlen[nq]=maxlen[p]+1。
這樣子就成功應對了這種情況。同樣也可以直接退出了。

先更到這裏,有一些經典運用的話,後面會補上。板子也會貼在後面的~~

後綴數組及其建圖