字尾樹組是乙個字串的所有字尾的排序陣列。字尾是指從某個位置 i 開始到整個串末尾結束的乙個子串。字串 r 的從 第 i 個字元開始的字尾表示為 suffix(i) ,也就是suffix(i)=r[i..len(r)] 。如下圖所示:
對於字串banana,所有的字尾字串是banana,anana,nana,ana,na,a。我們對其進行排序得到右邊的排序序列,其相應的索引值這時候就構成了字尾陣列[5, 3, 1, 0, 4, 2]。同時我們定義乙個名詞陣列rank。
其中,字尾陣列sa是乙個一維陣列,他儲存1…n的某個排列sa[1], sa[2], … , sa[n],並且s的n個字尾從小到大進行排序之後,把排好序的字尾的開頭位置放入sa中。名詞陣列rank[i]儲存的是suffix(i)在所有字尾中從小到大排列的名次。名詞陣列rank[i]和字尾陣列sa胡逆。一種通俗的解釋是:
字尾陣列是「排在第幾的是誰」,名次陣列是「你排第幾」。具體到banana這個例子的字尾陣列[5, 3, 1, 0, 4, 2],我們可以知道,排在第0位的字尾是從banana第5位開始的字尾a,排在第1位的字尾是從banana第3位開始的字尾ana,排在第2位的字尾是從banana第1位開始的字尾anana,以此類推。
在c中字尾陣列是通過指標實現的。我們嘗試在python中實現字尾陣列。
最簡單的方式是使用numpy包中自帶的argsort()函式,直接返回按照字典序排序後原始陣列的索引值
from numpy import argsort
defsuffixarray
(s):
if s == none
or len(s) == 0:
return
none
allsuffix =
for i in range(len(s)):
sa = argsort(allsuffix)
return sa
print(suffixarray("banana"))
輸出即為[5 3 1 0 4 2]。接著嘗試自己編寫**實現argsort()功能。
def
suffixarray
(s):
if s == none
or len(s) == 0:
return
none
allsuffix =
for i in range(len(s)):
sortedlist = sorted(allsuffix)
sa = [w[1] for w in sortedlist]
return sa
print(suffixarray("banana"))
我們首先生成所有字尾陣列,同時每個陣列匹配的index也一併寫入乙個新的陣列,這樣的空間複雜度就是o(n2),之後我們利用系統自帶的排序方法,一般時間效率為o(nlogn)。
下面我們利用字尾陣列解決最長公共子串問題。
首先我們在求字串a和b的最長公共子串的時候,可以將a和b連線到一起, 如果某乙個子串s是他們的公共字串,那麼s一定會在連線後字串字尾陣列中出現兩次,但是我們要注意的是,有可能會出現某個字串出現兩次,但它只是a或者b內部的某個重複子串,因此我們在連線a和b的時候,在中間加入乙個特殊字元「#」,即連線之後的字串變為a#b。這時候我們可以得到a#b的字尾陣列,字尾陣列中的每個位置對應的字尾字串一定是排好序的,所以最長的公共字串出現的位置一定是字尾陣列中相鄰元素對應的字尾字串的位置。同時得到 # 的位置,其中a和b的字尾子串開始的位置和 # 的差值一定是乙個大於0,乙個小於0,即乘積小於0。
整體步驟如下:
1. 首先將a、#、b連線形成新的字串s。
2. 求得s的字尾陣列以及#的位置indexofsharp。
3. 遍歷字尾陣列sa,求取字尾陣列中第i個位置對應的字尾字串和第i+1個位置對應的字尾字串,同時應該滿足(sa[i] - indexofsharp) * (sa[i + 1] - indexofsharp) 小於0,也就是這兩個字尾陣列分別位於 # 的兩側。
4. 然後比較新求得的兩個字尾子串的最長字首和之前儲存的最長字首maxlen的大小,如果大於maxlen,則更新maxlen,以及maxindex。最後返回相應的共同字串的長度以及字串。
具體python**如下:
# 利用字尾陣列解決最長公共子串問題
deflongestcommonsubstring2
(self, a, b):
if a == none
or b == none:
return
s = a + "#" + b
indexofsharp = len(a)
sa = self.suffixarray(s)
maxlen, maxindex = 0, 0
hascommon = false
for i in range(len(s) - 1):
# 判斷字尾陣列中兩個相鄰元素對應的字尾字串是否位於「#」兩側,即是否屬於a和b兩個不同字串
diff = (sa[i] - indexofsharp) * (sa[i + 1] - indexofsharp)
if diff < 0:
templen = self.comlen(s, sa[i], sa[i + 1])
if templen > maxlen:
maxlen = templen
maxindex = sa[i]
hascommon = true
return (maxlen, s[maxindex:maxindex + maxlen]) if hascommon else
false
# 得到乙個字串的字尾陣列
defsuffixarray
(self, s):
if s == none
or len(s) == 0:
return
none
allsuffix =
for i in range(len(s)):
sortedlist = sorted(allsuffix)
sa = [w[1] for w in sortedlist]
return sa
# 比較得到字尾陣列中兩個相鄰的元素分別對應的字尾字串的最長字首
defcomlen
(self, s, p, q):
j = 0
while j < len(s[p:]) and j < len(s[q:]) and s[p:p + j + 1] == s[q:q + j + 1]:
j += 1
return j
字尾陣列 用字尾處理字串
字尾陣列處理的是文字串。我們將文字串的每一條字尾拿出來,按照字典序排序,然後就可以處理字尾陣列了。字尾陣列sa i 表示的就是排名第i位的字尾的第乙個字元所在下標。這可能有點繞口,所以我們用樣例解釋一下,如對於文字串ababa,則字尾為ababa,baba,aba,ba,a,我們排序後就是 a,ab...
字尾陣列 最詳細講解
我們先看幾條定義 在字串s中,取任意i j,那麼在s中擷取從i到j的這一段就叫做s的乙個子串 字尾就是從字串的某個位置i到字串末尾的子串,我們定義以s的第i個字元為第乙個元素的字尾為suff i 把s的每個字尾按照字典序排序,字尾陣列sa i 就表示排名為i的字尾的起始位置的下標 而它的對映陣列rk...
字尾樹 字尾陣列
在字串處理當中,字尾樹和字尾陣列都是非常有力的工具,其中字尾樹大家了解得比較多,關於字尾陣列則很少見於國內的資料。其實字尾陣列是字尾樹的乙個非 常精巧的替代品,它比字尾樹容易程式設計實現,能夠實現字尾樹的很多功能而時間複雜度也不太遜色,並且,它比字尾樹所占用的空間小很多。可以說,在資訊學競賽 中字尾...