字尾樹與最長回文子串

2021-06-19 16:44:12 字數 3799 閱讀 8783

字尾樹與最長回文子串

回文串:正反一樣的字串.

如abcdcba,反過來是還是abcdcba.

回文子串:dabcaea中有兩個回文子串(不包括長度為1的),即abca,aea.

長度abca>aea.

那麼字串dabcaea的最長回文子串就是abca.

解決方法:

1.      遍歷所有的子串.

長度n的字串,共有2^n個子串,去除長度為0,1的,也有2^n-n-1個.遍歷的時間複雜度是o(2^n),還要加上判斷是否回文的函式.

2.      以某個字元為中心,向兩邊擴散尋找.

當某個字元為中心較短的子串不滿足要求時,其更長的子串也不可能滿足要求,減少了遍歷演算法中許多不必要的計算.

時間複雜度為o(n^2).

3.      字尾樹

字尾樹是解決許多字串問題的利器.下面介紹字尾樹.

trie樹,又稱字典樹,單詞查詢樹或者字首樹,是乙個用於快速檢索的多叉樹. 典型應用是用於統計和排序大量的字串(但不僅限於字串),所以經常被搜尋引擎系統用於文字詞頻統計(@july)。如英文本母的字典樹是乙個26叉樹,數字的字典樹是乙個10叉樹.

trie利用字串的公共字首來節省儲存空間,並且能夠以空間換時間.

盜用別人的一張圖, 給出一組單詞,inn, int, at, age, adv, ant, 我們可以得到下面的trie:

根據上面的trie,我們可以

1.      計算任意某兩個單詞的最長公共字首,就是我們待會要詳細說明的lca演算法.

2.      快速計算某個單詞的出現次數.也就是我們上面說的以空間換時間的方法,建樹與查詢同時進行,每次根據單詞查詢或新建節點,計數.時間複雜度為o(n*length),length表示字串的平均長度.

我們再通過trie,即字首樹,來引出字尾樹.

字尾的含義:

對於字串xmadamyx,他的字尾是從後往前數的任意連續序列,如x,yx,myx...xmadamyx等都是其字尾,我們記作s[i],表示從第i個到最後的字尾.

s[1], xmadamyx, 也就是字串本身,起始位置為1

s[2], madamyx,起始位置為2

s[3], adamyx,起始位置為3

s[4], damyx,起始位置為4

s[5], amyx,起始位置為5

s[6], myx,起始位置為6

s[7], yx,起始位置為7

s[8], x,起始位置為8

接著,我們用上面trie的方法,把乙個字串的所有字尾建立trie.再盜用一張圖.如下:

正如我們上面說的,一般trie是許多字串生成的字首樹,而這裡我們用乙個字串生成n個字尾子串,再用字尾子串生成trie.

上圖中,有許多中間節點是多餘的,我們要用最少的節點來表示,並且所有的字尾都是葉子節點,那麼就做如下的壓縮:

1.      每個節點可以儲存多個字母.

2.      字串後加乙個結尾,如』$』,那麼就不會有任意字尾是其他字尾的字首,壓縮的時候就不會有字尾被壓縮掉.

於是,我們得到了壓縮的字尾樹:

繼續盜圖:

於是我們概括為 ( 字尾樹=字尾子串們+ trie ).

那麼字尾子串的trie與最長回文有什麼關係呢?

s1=xmadamyx反過來是s2=xymadamx,s1(4)=damyx,s2(5)=damx,他們的最長公共字首是dam,也就是最長回文子串的madam的半徑.

於是,我們得到了解決最長回文子串的方法.

s1 和翻轉 s2,生成的所有字尾子串壓入到trie中,計算s1(i)與s2(n-i+1)節點的最低公共祖先,即其最長公共字首,再得到最長回文子串.

如下圖,用s1+』$』 即翻轉後的s2+』#』建立字尾樹,得到如下的樹.

根據我們上面的描述,我們現在的任務是計算s1(i)和s2(n-i+1)節點的最低公共祖先.如上圖1$與8#,2$與7#...等的lca,最後得出最長的.

下面我們具體的說說lca問題,即計算任意兩個單詞的最長公共字首.

lca(lowest common ancestor: ):最低相同祖先,即樹型結構中兩個節點離root節點最近的公共祖先節點.如下圖:

兩個黃色節點的lca為圖中所示藍色節點.

方法: tarjan'salgorithm

tarjan的方法,深度優先遍歷樹,用並查集的思想層級記錄遍歷過節點的祖先情況,具體看下圖及**:

**:可見演算法**:

#includeusing namespace std;

bool adj[9][9]; // adjacency matrix,陣列存樹

bool visit[9]; // 訪問判斷

int lca[9][9]; // 任意兩點之間的lca

int p[9];//並查集陣列,表示其夫節點

int find(int x)

void dfs(int x)

{ if (visit[x]) return;

visit[x] = true;

cout< x >> y)

cout << x<

通過上面的翻轉,建樹,計算lca.我們最後得到了以每個字元為中心的最長回文.最後在統計得到總的最長回文.

上述插敘任意兩個節點的lca時間複雜度為o(n).但是我們傳統的建樹過程,時間複雜度應該是o(n^2).這樣,如果是一般較少的查詢,建樹的時間複雜度會抵消插敘帶來的遍歷.

後來有人發明了時間複雜度為o(n)的建樹方法-ukkonen,我們就可以大膽地使用字尾樹做各種用途了.有興趣的可以繼續深入研究

最後是字尾樹的其他用途(直接拷貝)

(1).查詢字串o是否在字串s中。 

方案:用s構造字尾樹,按在trie中搜尋字串的方法搜尋o即可。 

原理:若o在s中,則o必然是s的某個字尾的字首。 

例如s: leconte,查詢o: con是否在s中,則o(con)必然是s(leconte)的字尾之一conte的字首.有了這個前提,採用trie搜尋的方法就不難理解了。 

(2). 指定字串t在字串s中的重複次數。 

方案:用s+』$'構造字尾樹,搜尋t節點下的葉節點數目即為重複次數 

原理:如果t在s中重複了兩次,則s應有兩個字尾以t為字首,重複次數就自然統計出來了。 

(3). 字串s中的最長重複子串 

方案:原理同2,具體做法就是找到最深的非葉節點。 

這個深是指從root所經歷過的字元個數,最深非葉節點所經歷的字串起來就是最長重複子串。 

為什麼要非葉節點呢?因為既然是要重複,當然葉節點個數要》=2。 

(4). 兩個字串s1,s2的最長公共部分 

方案:將s1#s2$作為字串壓入字尾樹,找到最深的非葉節點,且該節點的葉節點既有#也有$(無#)。

最長回文子串與最長回文子串行

題目1 求最長回文子串 題目2 求回文子串數量 忽略兩個題目對於返回結果的不同要求 前者返回最長的結果,後者返回最長的結果對應的長度即可 dp陣列的定義的區別 最長回文子串 dp i j 表示的是string s中從i到j的子串 用python語法就是s i j 1 是否為回文子串。最長回文子串行 ...

最長回文子串 最長回文子串行

1.最長回文子串行 可以不連續 include include include include using namespace std 遞迴方法,求解最長回文子串行 intlps char str,int i,int j intmain include include include using n...

最長回文子串

描述 輸入乙個字串,求出其中最長的回文子串。子串的含義是 在原串連續出現的字串片段。回文的含義是 正著看和倒著看是相同的,如abba和abbebba。在判斷是要求忽略所有的標點和空格,且忽略大小寫,但輸出時按原樣輸出 首尾不要輸出多餘的字串 輸入字串長度大於等於1小於等於5000,且單獨佔一行 如果...