我很懒的 @2008-03-10 16:47
后缀树和后缀数组简直就是 ACM 选手必备的知识啊,我已经在两次比赛中碰到过相关的问题了。我甚至还写过一篇应用的文章,可是我真是井底之蛙啊,那时我还不知道这个叫后缀数组,还有更好的构造算法,还有很多的应用。最近终于好好在这方面扫了个盲,在此小小地总结一下。
假设有一个长度为 n 的字符串 T[0 ... n);S(i) 表示 T 的从下标 i 开始的后缀,即 T[i ... n)。那么 T 的后缀数组就是把 S(i) ~ S(n - 1) 这 n 个后缀按字典序排好序的一个数组。它对于查找 T 的子串之类的问题是很有用的。问题就在于怎样快速地把这些后缀排好序。
最简单的方法就是把所有 S(i) 快速排序。快速排序本身的时间是 O(n log n),但是由于排序的对象是一个个字符串,所以每次比较的时间在最差情况下都会变成线性的(也就是 O(n) 的),因此总的时间在最差情况下可能会升到 O(n2) 左右,这就很慢了。对此,我学到了三个更快的算法。
1. Ukkonen 算法
Ukkonen 算法先用 O(n) 的时间构造一棵后缀树,然后再用 O(n) 的时间从后缀树得到后缀数组。在这个网址,介绍了作者 EskoUkkonen,并列出了他的一些论文;其中的一篇《On-line construction of suffix-trees》是可以下载的,里面就讲解了什么是后缀树,怎样在 O(n) 的时间内构造它,以及怎样从它得到后缀数组。
不过我一开始还没发现这篇论文,我是从 Dan Gusfield 的《Algorithms on Strings, Trees andSequences - COMPUTER SCIENCE AND COMPUTATIONAL BIOLOGY》这本书里学到这个算法的。这本书在中国没的卖,想买的话,可以找代购网站去Amazon 买。我是在 eMule 上搜到并下载的。这本书中的这节内容讲得还可以,虽然我觉得它示例比较少,但是花了点功夫还是看懂了。学会了之后,原作者的论文我就没有仔细看过了,所以没法评论。
Ukkonen 算法还是比较复杂的,代码比较长;而且后缀树这个结构本身也比较费空间。总而言之,虽然该算法在理论上是最快的,后缀树也是一个很优美的结构,但是在许多实际应用中不是很实惠。
然而,一开始我还不知道别的算法时,还是把它实现了出来(代码 1、代码 2)。(我写了两个版本,它们的不同点在于每个节点的子节点的存放方式。代码 1 是用数组,代码 2 是用链表。用数组的话,查找指定的子节点很快,只要 O(1);但是比较费空间。用链表的话,省空间,但是查找子节点比较慢,只能线性地查找,不过一般情况下问题不大。实际上,我在 PKU 3415 这道题中,用数组反而比用链表慢,可能前者分配空间所花的时间比较多吧。)
2. DC3 算法
我在 Google 上搜到了这篇论文,《Linear Work Suffix ArrayConstruction》,其中介绍了一个可以在O(3n) 的时间内构造出后缀数组的算法,叫作 DC3 (Difference Cover mod 3) 算法。
该算法的基本原理大致是这样的。针对所有后缀的前 3 个字符,做基数排序;对于不满 3 个字符的后缀,排序时在后面补 0(这里的 0 是结束符,在 T 中不能出现;0 的字典序最优先);排序时还要包括进从结束符(即 T[n])开始的后缀 S(n): “000”。如果所有后缀的前 3 个字符都不完全相同,那么这一次就排好了,最后去掉多余的 “000”后缀(它一定排在第一个),就得到答案了,时间是 O(3n)。如果存在前 3 个字符相同的,则需要生成一个名次数组 R, R(i) 表示 S(i) 在排好序后位于第几名(名次从 1 开始计),接着再用上述方法递归地求 R[0 ... n] 的后缀数组,其结果和 T 的后缀数组是完全对应的,也就是说 SR(i) 排在第几位,则 S(i) 也应该排在第几位。但问题是如果这样递归层数多了,时间也就大大增加了。
接下来,在上述算法的基础上,需要一个优化。首先,只对满足 i mod 3= 1 或 i mod 3 = 2 的那些 S(i) 按照前 3 个字符进行基数排序;如果这其中有前 3 个字符相同的,同样也需要递归地求它们的名次数组的后缀数组。排好了 i mod 3 = 1、2 的后缀之后,就可以得到一个总的名次数组 R,其中那些 i mod 3= 0 的后缀的名次还是未知的。接着对于所有 i mod 3 = 0 的 S(i),靠 T[i] 和 R(i + 1) 这两个关键字就可以对它们排序了。最后把排好序的 mod 3 = 1、2 和 mod 3 = 0 的后缀归并起来就是答案了。归并的时候,比较两个后缀 S(i) 和 S(j) 的方法也是看它们的前 3 个字符,如果都相同,那么比较 R(i + 1) 和 R(j + 1),若不可比(其中有一个是未知的)则再比较 R(i + 2) 和 R(j + 2)。
有了以上的优化,即使当中出现了需要递归的情况,每次递归求解的字符串长度也只有原来的 2 / 3,那么即使递归的层数再多,总的时间之和也是会收敛的。
以上我只是潦草地介绍一下,具体的还是自己看论文吧。论文写得还是蛮清楚的。尤其是最后有一个用 C++ 实现的代码,其中有很多细节实现地很巧妙,很值得学习。
3. 倍增算法
我是从 IOI 2004 国家集训队论文集中的一篇名为《后缀数组》的文章中学到这个算法的。该文章在Google 上搜得到,讲得还是蛮清楚的。我在此就不多介绍了,请自己看文章。
倍增算法最大的优点是实现简单,速度也还可以,O(n log n)。如果程序的时间要求不是很紧的话,应该作为首选的算法。这里是我对倍增算法的实现。
4. 多个字符串的后缀数组
在很多问题中,都需要求多个字符串的后缀数组,也就是把多个字符串的所有后缀都放在一起排序。这个结构对于查找公共子串之类的问题是很有用的。后缀树是可以表示多个字符串的,但是 DC3 算法和倍增算法都只能求单个字符串的后缀数组。
其实多个字符串的后缀数组可以转化成单个字符串的后缀数组。比如要求 “abc”和 “def” 这两个字符串的后缀数组,可以转化成求“abc1def” 的后缀数组。其中 1 是字典顺仅次于结束符 0 的字符,它也不出现在任何字符串中。这样求出来的后缀数组和 “abc” 与“def”的后缀数组是等价的;只是多了一个以 1 开头的后缀,但它一定排序在最前面,很容易去掉。在倍增算法中,用 0 替代 1 好像也可以;在 DC3 算法中好像不能用 0 替代 1,但是我忘记怎么重现那个错误了,所以现在也不好说。但是用 1 肯定是没错的,这样符合 “结束符在字符串中不出现” 的原则。
这篇文章我写得比较潦草,因为我引用的几篇文章本身都写得很清楚了,我确实没有什么新发现。所以到此为止吧。
关键词/Tags: acm 后缀数组 后缀树 pku 倍增算法 ukkonen dc3
曾经的这一天...
[代码] 用 Ukkonen 算法构造后缀树/后缀数组,O(n),用数组存放子节点
我很懒的 @1985-08-05 15:30
本文是这篇文章的附件。
/////////////////////////////////////////////////////////////////
//Suffix Tree and Suffix Array with UkkonenAlgorithm.
//Store child nodes in array.
/////////////////////////////////////////////////////////////////
#include
#include //used by suffix treenode.
#include
using namespace std;
struct Suffix { const char* str; int from; };
const int ALPHABET_SIZE = 26 * 2 + 1;
struct InNode;
struct SfxNode //Suffix tree node
{ const char *l, *r;//[l, r): edge label from parent to this node.
InNode* prnt;//parent
virtual bool isLeaf() = 0; };
struct InNode: public SfxNode {//Internalnode (non-leaf node)
//for character X and string A, if this node's label is XA,
//then the suffix link is the node with label A, if any.
InNode* sfxLink;//suffix link
SfxNode* ch[ALPHABET_SIZE];//children
SfxNode*& child(char c)
{ if ('{post.content}' == c) { returnch[0]; }
return c < 'a'? ch[c - 'A' + 1]: ch[c - 'a' + 27]; }
bool isLeaf() { return false; }
};
struct Leaf: public SfxNode//Suffix treeleaf
{ list
bool isLeaf() { return true; } };
InNode g_internal[200000 + 100]; //Ask formemory once and allocate
Leaf g_leaf[200000 + 100]; //them my self, to make the
int g_inCnt = 0, g_leafCnt = 0; //tree destruction fast.
InNode* newInNode(const char* l = NULL,const char* r = NULL)
{ InNode* p = &g_internal[g_inCnt++];
p->l = l; p->r = r; p->sfxLink = p->prnt = NULL;
memset( p->ch, 0, sizeof(p->ch) );
return p; }
Leaf* newLeaf(const char* l = NULL, const char*r = NULL)
{ Leaf* p = &g_leaf[g_leafCnt++];
p->l = l; p->r = r; p->from.clear();
return p; }
list
class SuffixTree {
public:
SuffixTree(): m_root( newInNode() ), m_texts(), m_lens() {}
~SuffixTree() { clear(); }
//Don't free the space of the added string
//before the last string is added.
void addText(const char* text) {
m_text = m_i = text; m_leafCnt =0; m_p = m_root;
m_root->l = m_root->r = m_text;
m_len = strlen(text);
for (int i = 0; i <= m_len; i++) {
m_newIn = NULL;
for (int j = m_leafCnt; j <= i; j++)
{ if ( !extend(m_text + j,m_text + i) ) { break; } }
}
m_texts.push_back(m_text); m_lens.push_back(m_len);
}
void clear() { g_inCnt = g_leafCnt = 0; m_root = newInNode();
m_texts.clear(); m_lens.clear(); }
//Write the two arrays to construct a suffix array:
//sfx: the suffixes in lexigraphical order.
//lcp[i]: longest common prefix of sfx[i - 1] and sfx[i].
void toSuffixArray(Suffix* sfx, int* lcp) const {
Node* p = m_root;
int i = 0, depth = 0, sfxI = 0, cp = 0;
g_stack.clear(); g_stack.push_back(0);
while ( !g_stack.empty() ) {
if ( p->isLeaf() ) {
Leaf* leaf = (Leaf*)p;
if (depth > 1) {
for(list
leaf->from.end() != it; it++)
{ sfx[sfxI].from = *it;
sfx[sfxI].str =m_texts[*it]+m_lens[*it]-depth+1;
lcp[sfxI++] = cp; cp = depth - 1; }
}
i = g_stack.back(); i++; g_stack.pop_back();
depth -= p->r -p->l; p = p->prnt; cp = depth;
}
else {
InNode* in = (InNode*)p;
while ( i < ALPHABET_SIZE&& !in->ch[i] ) { i++; }
if (ALPHABET_SIZE == i)//Allchildren are visited.
{ i = g_stack.back(); i++; g_stack.pop_back();
depth -= p->r -p->l; p = p->prnt; cp = depth; }
else { p = in->ch[i]; depth += p->r - p->l;
g_stack.push_back(i); i = 0; }
}
}
}
private:
typedef SfxNode Node;
//Go along string m_text[l, r) starting from node p.
void goStr(const char* l, const char* r) {
m_i = m_p->r;
while (l < r)
{ m_p = ( (InNode*)m_p)->child(*l);//There must be a child.
if (r-l <= m_p->r - m_p->l) { m_i = m_p->l + (r-l); l=r; }
else { m_i = m_p->r; l +=m_p->r - m_p->l; } }
}
//Return true if new leaf is added.
bool extend(const char* i, const char* r) {
if (m_i < m_p->r) {
const char* l;
if (*m_i == *r) {//implicit extension, no new leaf added.
if (*r) { m_i++; return false; }
( (Leaf*)m_p)->from.push_back( m_texts.size() );
l = r - (m_p->r - m_p->l- 1);
}
else {
//Insert a new internal nodeand add a new leaf.
InNode* in =newInNode(m_p->l, m_i);
m_p->prnt->child(*m_p->l) = in; in->prnt = m_p->prnt;
in->child(*m_i) = m_p; m_p->prnt = in; m_p->l = m_i;
Leaf* leaf = newLeaf(r, m_text+ m_len + 1);
in->child(*r) = leaf; leaf->prnt = in;
leaf->from.push_back(m_texts.size() ); m_leafCnt++;
//This new internal node may besuffix link of others.
if (m_newIn) {m_newIn->sfxLink = in; }
m_p = m_newIn = in;
l = r - (m_p->r -m_p->l);
}
//Find the position of next extension.
InNode* p = m_p->prnt; m_p =p;
if (p->sfxLink) { m_p = p->sfxLink; } else { l++; }
goStr(l, r);
}
else {//in condition that m_i == m_p->r
InNode* p = (InNode*)m_p;//now m_p must be internal.
if (m_newIn) { m_newIn->sfxLink = p; m_newIn = NULL; }
Node* ch = p->child(*r);
if (ch)
{ if (*r) { m_p = ch; m_i = m_p->l + 1; return false; }
( (Leaf*)ch)->from.push_back( m_texts.size() ); }
else { Leaf* leaf = newLeaf(r, m_text + m_len + 1);
p->child(*r) = leaf; leaf->prnt = p; m_leafCnt++;
leaf->from.push_back(m_texts.size() ); }
if (i < r) { m_p = p->sfxLink; goStr(NULL, NULL); }
}
return true;
}
InNode* m_root;
vector
//the following members are to help the extensions.
Node* m_p; InNode* m_newIn;
const char *m_i, *m_text;
int m_leafCnt, m_len;
};
//Test suite and usage example
#include
int main() {
Suffix sa[100]; int lcp[100];
char a[] = "xabxa", b[] = "babxba";
SuffixTree t; t.addText(a); t.addText(b);
t.toSuffixArray(sa, lcp);
int cnt = strlen(a) + strlen(b);
for (int i = 0; i < cnt; i++)
{ cout << sa[i].from<<" "<< sa[i].str <<" "< return0; //output: 0 a 0 // 1 a 1 // 0 abxa 1 // 1 abxba 3 // 1 ba 0 // 1 babxba 2 // 0 bxa 1 // 1 bxba 2 // 0 xa 0 // 0 xabxa 2 // 1 xba 1 } 关键词/Tags: acm 后缀数组 后缀树 代码] 用倍增算法构造后缀数组,O(n log n) 我很懒的 @1985-08-05 17:04 本文是这篇文章的附件。 ///////////////////////////////////////////////////////////////// //Constructing Suffix Array with DoublingAlgorithm, O(n log n). ///////////////////////////////////////////////////////////////// #include #include using namespace std; const int MAX_SFX = 210000; struct Sfx { int i; int key[2]; bool operator < (const Sfx& s) const { return key[0] < s.key[0] || key[0] == s.key[0] &&key[1] < s.key[1]; } }; int g_buf[MAX_SFX + 1]; Sfx g_tempSfx[2][MAX_SFX], *g_sa =g_tempSfx[0]; void cSort(Sfx* in, int n, int key, Sfx*out) { int* cnt = g_buf; memset( cnt, 0,sizeof(int) * (n + 1) ); for (int i = 0; i < n; i++) { cnt[ in[i].key[key] ]++; } for (int i = 1; i <= n; i++) { cnt[i] += cnt[i - 1]; } for (int i = n - 1; i >= 0; i--) { out[ --cnt[ in[i].key[key] ] ]= in[i]; } } //Build a suffix array from string 'text'whose length is 'len'. //write the result into global array'g_sa'. void buildSA(char* text, int len) { Sfx *temp = g_tempSfx[1]; int* rank = g_buf; for (int i = 0; i < len; i++) { g_sa[i].i = g_sa[i].key[1] = i; g_sa[i].key[0] = text[i]; } sort(g_sa, g_sa + len); for (int i = 0; i < len; i++) { g_sa[i].key[1] = 0; } int wid = 1; while (wid < len) { rank[ g_sa[0].i ] = 1; for (int i = 1; i < len; i++) { rank[ g_sa[i].i ] = rank[g_sa[i - 1].i ]; if ( g_sa[i-1] < g_sa[i] ) { rank[ g_sa[i].i ]++; } } for (int i = 0; i < len; i++) { g_sa[i].i = i; g_sa[i].key[0] =rank[i]; g_sa[i].key[1] = i + wid < len?rank[i + wid]: 0; } cSort(g_sa, len, 1, temp); cSort(temp, len, 0, g_sa); wid *= 2; } } int getLCP(char* a, char* b) { int l=0; while(*a && *b && *a==*b) { l++; a++; b++; } return l; } void getLCP(char* text, Sfx* sfx, int len,int* lcp) { int* rank = g_buf; for (int i=0, r=0; i < len; i++, r++) { rank[ sfx[i].i ] = r; } lcp[0] = 0; if (rank[0]) { lcp[ rank[0] ] = getLCP( text, text + sfx[ rank[0]-1 ].i ); } for (int i = 1; i < len; i++) { if ( !rank[i] ) { continue; } if (lcp[ rank[i - 1] ] <= 1) { lcp[ rank[i] ] = getLCP( text+i, text+sfx[ rank[i]-1 ].i ); } else { int L = lcp[ rank[i - 1] ] - 1; lcp[rank[i]] = L+getLCP(text+i+L, text+sfx[rank[i]-1].i+L); } } } //Test suite and usage example #include using namespace std; int main() { char str[] = "aabbaa{post.content}ababab"; int from[] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1}; int lcp[13]; buildSA(str, 13); getLCP(str,g_sa, 13, lcp); for (int i=1; i<13; i++)//The first suffix is useless (empty). {cout< return 0;//output: 0 a 0 // 0 aa 1 // 0 aabbaa 2 // 1 ab 1 // 1 abab 2 // 1 ababab 4 // 0 abbaa 2 // 1 b 0 // 0 baa 1 // 1 bab 2 // 1 babab 3 // 0 bbaa 1 } 我很懒的 @2008-03-10 16:47 后缀树和后缀数组简直就是 ACM 选手必备的知识啊,我已经在两次比赛中碰到过相关的问题了。我甚至还写过一篇应用的文章,可是我真是井底之蛙啊,那时我还不知道这个叫后缀数组,还有更好的构造算法,还有很多的应用。最近终于好好在这方面扫了个盲,在此小小地总结一下。 假设有一个长度为 n 的字符串 T[0 ... n);S(i) 表示 T 的从下标 i 开始的后缀,即 T[i ... n)。那么 T 的后缀数组就是把 S(i) ~ S(n - 1) 这 n 个后缀按字典序排好序的一个数组。它对于查找 T 的子串之类的问题是很有用的。问题就在于怎样快速地把这些后缀排好序。 最简单的方法就是把所有 S(i) 快速排序。快速排序本身的时间是 O(n log n),但是由于排序的对象是一个个字符串,所以每次比较的时间在最差情况下都会变成线性的(也就是 O(n) 的),因此总的时间在最差情况下可能会升到 O(n2) 左右,这就很慢了。对此,我学到了三个更快的算法。 1. Ukkonen 算法 Ukkonen 算法先用 O(n) 的时间构造一棵后缀树,然后再用 O(n) 的时间从后缀树得到后缀数组。在这个网址,介绍了作者 EskoUkkonen,并列出了他的一些论文;其中的一篇《On-line construction of suffix-trees》是可以下载的,里面就讲解了什么是后缀树,怎样在 O(n) 的时间内构造它,以及怎样从它得到后缀数组。 不过我一开始还没发现这篇论文,我是从 Dan Gusfield 的《Algorithms on Strings, Trees andSequences - COMPUTER SCIENCE AND COMPUTATIONAL BIOLOGY》这本书里学到这个算法的。这本书在中国没的卖,想买的话,可以找代购网站去Amazon 买。我是在 eMule 上搜到并下载的。这本书中的这节内容讲得还可以,虽然我觉得它示例比较少,但是花了点功夫还是看懂了。学会了之后,原作者的论文我就没有仔细看过了,所以没法评论。 Ukkonen 算法还是比较复杂的,代码比较长;而且后缀树这个结构本身也比较费空间。总而言之,虽然该算法在理论上是最快的,后缀树也是一个很优美的结构,但是在许多实际应用中不是很实惠。 然而,一开始我还不知道别的算法时,还是把它实现了出来(代码 1、代码 2)。(我写了两个版本,它们的不同点在于每个节点的子节点的存放方式。代码 1 是用数组,代码 2 是用链表。用数组的话,查找指定的子节点很快,只要 O(1);但是比较费空间。用链表的话,省空间,但是查找子节点比较慢,只能线性地查找,不过一般情况下问题不大。实际上,我在 PKU 3415 这道题中,用数组反而比用链表慢,可能前者分配空间所花的时间比较多吧。) 2. DC3 算法 我在 Google 上搜到了这篇论文,《Linear Work Suffix ArrayConstruction》,其中介绍了一个可以在O(3n) 的时间内构造出后缀数组的算法,叫作 DC3 (Difference Cover mod 3) 算法。 该算法的基本原理大致是这样的。针对所有后缀的前 3 个字符,做基数排序;对于不满 3 个字符的后缀,排序时在后面补 0(这里的 0 是结束符,在 T 中不能出现;0 的字典序最优先);排序时还要包括进从结束符(即 T[n])开始的后缀 S(n): “000”。如果所有后缀的前 3 个字符都不完全相同,那么这一次就排好了,最后去掉多余的 “000”后缀(它一定排在第一个),就得到答案了,时间是 O(3n)。如果存在前 3 个字符相同的,则需要生成一个名次数组 R, R(i) 表示 S(i) 在排好序后位于第几名(名次从 1 开始计),接着再用上述方法递归地求 R[0 ... n] 的后缀数组,其结果和 T 的后缀数组是完全对应的,也就是说 SR(i) 排在第几位,则 S(i) 也应该排在第几位。但问题是如果这样递归层数多了,时间也就大大增加了。 接下来,在上述算法的基础上,需要一个优化。首先,只对满足 i mod 3= 1 或 i mod 3 = 2 的那些 S(i) 按照前 3 个字符进行基数排序;如果这其中有前 3 个字符相同的,同样也需要递归地求它们的名次数组的后缀数组。排好了 i mod 3 = 1、2 的后缀之后,就可以得到一个总的名次数组 R,其中那些 i mod 3= 0 的后缀的名次还是未知的。接着对于所有 i mod 3 = 0 的 S(i),靠 T[i] 和 R(i + 1) 这两个关键字就可以对它们排序了。最后把排好序的 mod 3 = 1、2 和 mod 3 = 0 的后缀归并起来就是答案了。归并的时候,比较两个后缀 S(i) 和 S(j) 的方法也是看它们的前 3 个字符,如果都相同,那么比较 R(i + 1) 和 R(j + 1),若不可比(其中有一个是未知的)则再比较 R(i + 2) 和 R(j + 2)。 有了以上的优化,即使当中出现了需要递归的情况,每次递归求解的字符串长度也只有原来的 2 / 3,那么即使递归的层数再多,总的时间之和也是会收敛的。 以上我只是潦草地介绍一下,具体的还是自己看论文吧。论文写得还是蛮清楚的。尤其是最后有一个用 C++ 实现的代码,其中有很多细节实现地很巧妙,很值得学习。 3. 倍增算法 我是从 IOI 2004 国家集训队论文集中的一篇名为《后缀数组》的文章中学到这个算法的。该文章在Google 上搜得到,讲得还是蛮清楚的。我在此就不多介绍了,请自己看文章。 倍增算法最大的优点是实现简单,速度也还可以,O(n log n)。如果程序的时间要求不是很紧的话,应该作为首选的算法。这里是我对倍增算法的实现。 4. 多个字符串的后缀数组 在很多问题中,都需要求多个字符串的后缀数组,也就是把多个字符串的所有后缀都放在一起排序。这个结构对于查找公共子串之类的问题是很有用的。后缀树是可以表示多个字符串的,但是 DC3 算法和倍增算法都只能求单个字符串的后缀数组。 其实多个字符串的后缀数组可以转化成单个字符串的后缀数组。比如要求 “abc”和 “def” 这两个字符串的后缀数组,可以转化成求“abc1def” 的后缀数组。其中 1 是字典顺仅次于结束符 0 的字符,它也不出现在任何字符串中。这样求出来的后缀数组和 “abc” 与“def”的后缀数组是等价的;只是多了一个以 1 开头的后缀,但它一定排序在最前面,很容易去掉。在倍增算法中,用 0 替代 1 好像也可以;在 DC3 算法中好像不能用 0 替代 1,但是我忘记怎么重现那个错误了,所以现在也不好说。但是用 1 肯定是没错的,这样符合 “结束符在字符串中不出现” 的原则。 这篇文章我写得比较潦草,因为我引用的几篇文章本身都写得很清楚了,我确实没有什么新发现。所以到此为止吧。 关键词/Tags: acm 后缀数组 后缀树 pku 倍增算法 ukkonendc3 曾经的这一天... [代码] 用 Ukkonen 算法构造后缀树/后缀数组,O(n),用数组存放子节点 我很懒的 @1985-08-05 15:30 本文是这篇文章的附件。 ///////////////////////////////////////////////////////////////// //Suffix Tree and Suffix Array with UkkonenAlgorithm. //Store child nodes in array. ///////////////////////////////////////////////////////////////// #include #include #include using namespace std; struct Suffix { const char* str; int from; }; const int ALPHABET_SIZE = 26 * 2 + 1; struct InNode; struct SfxNode //Suffix tree node { const char *l, *r;//[l, r): edge label from parent to this node. InNode* prnt;//parent virtual bool isLeaf() = 0; }; struct InNode: public SfxNode {//Internalnode (non-leaf node) //for character X and string A, if this node's label is XA, //then the suffix link is the node with label A, if any. InNode* sfxLink;//suffix link SfxNode* ch[ALPHABET_SIZE];//children SfxNode*& child(char c) { if ('{post.content}' == c) { return ch[0]; } return c < 'a'? ch[c - 'A' + 1]: ch[c - 'a' + 27]; } bool isLeaf() { return false; } }; struct Leaf: public SfxNode//Suffix treeleaf { list bool isLeaf() { return true; } }; InNode g_internal[200000 + 100]; //Ask formemory once and allocate Leaf g_leaf[200000 + 100]; //them my self, to make the int g_inCnt = 0, g_leafCnt = 0; //tree destruction fast. InNode* newInNode(const char* l = NULL,const char* r = NULL) { InNode* p = &g_internal[g_inCnt++]; p->l = l; p->r = r; p->sfxLink = p->prnt = NULL; memset( p->ch, 0, sizeof(p->ch) ); return p; } Leaf* newLeaf(const char* l = NULL, constchar* r = NULL) { Leaf* p = &g_leaf[g_leafCnt++]; p->l = l; p->r = r; p->from.clear(); return p; } list class SuffixTree { public: SuffixTree(): m_root( newInNode() ), m_texts(), m_lens() {} ~SuffixTree() { clear(); } //Don't free the space of the added string //before the last string is added. void addText(const char* text) { m_text = m_i = text; m_leafCnt = 0; m_p = m_root; m_root->l = m_root->r = m_text; m_len = strlen(text); for (int i = 0; i <= m_len; i++) { m_newIn = NULL; for (int j = m_leafCnt; j <= i; j++) { if ( !extend(m_text +j, m_text + i) ) { break; } } } m_texts.push_back(m_text); m_lens.push_back(m_len); } void clear() { g_inCnt = g_leafCnt = 0; m_root = newInNode(); m_texts.clear(); m_lens.clear(); } //Write the two arrays to construct a suffix array: //sfx: the suffixes in lexigraphical order. //lcp[i]: longest common prefix of sfx[i - 1] and sfx[i]. void toSuffixArray(Suffix* sfx, int* lcp) const { Node* p = m_root; int i = 0, depth = 0, sfxI = 0, cp = 0; g_stack.clear(); g_stack.push_back(0); while ( !g_stack.empty() ) { if ( p->isLeaf() ) { Leaf* leaf = (Leaf*)p; if (depth > 1) { for(list leaf->from.end() !=it; it++) { sfx[sfxI].from = *it; sfx[sfxI].str =m_texts[*it]+m_lens[*it]-depth+1; lcp[sfxI++] = cp; cp = depth - 1; } } i = g_stack.back(); i++; g_stack.pop_back(); depth -= p->r -p->l; p = p->prnt; cp = depth; } else { InNode* in = (InNode*)p; while ( i < ALPHABET_SIZE&& !in->ch[i] ) { i++; } if (ALPHABET_SIZE == i)//Allchildren are visited. { i = g_stack.back(); i++; g_stack.pop_back(); depth -= p->r -p->l; p = p->prnt; cp = depth; } else { p = in->ch[i]; depth += p->r - p->l; g_stack.push_back(i); i = 0; } } } } private: typedef SfxNode Node; //Go along string m_text[l, r) starting from node p. void goStr(const char* l, const char* r) { m_i = m_p->r; while (l < r) { m_p = ( (InNode*)m_p)->child(*l);//There must be a child. if (r-l <= m_p->r - m_p->l) { m_i = m_p->l + (r-l); l=r; } else { m_i = m_p->r; l +=m_p->r - m_p->l; } } } //Return true if new leaf is added. bool extend(const char* i, const char* r) { if (m_i < m_p->r) { const char* l; if (*m_i == *r) {//implicitextension, no new leaf added. if (*r) { m_i++; return false; } ( (Leaf*)m_p)->from.push_back( m_texts.size() ); l = r - (m_p->r - m_p->l- 1); } else { //Insert a new internal nodeand add a new leaf. InNode* in =newInNode(m_p->l, m_i); m_p->prnt->child(*m_p->l) = in; in->prnt = m_p->prnt; in->child(*m_i) = m_p; m_p->prnt = in; m_p->l = m_i; Leaf* leaf = newLeaf(r, m_text+ m_len + 1); in->child(*r) = leaf; leaf->prnt = in; leaf->from.push_back(m_texts.size() ); m_leafCnt++; //This new internal node may besuffix link of others. if (m_newIn) {m_newIn->sfxLink = in; } m_p = m_newIn = in; l = r - (m_p->r -m_p->l); } //Find the position of next extension. InNode* p = m_p->prnt; m_p =p; if (p->sfxLink) { m_p = p->sfxLink; } else { l++; } goStr(l, r); } else {//in condition that m_i == m_p->r InNode* p = (InNode*)m_p;//now m_p must be internal. if (m_newIn) { m_newIn->sfxLink = p; m_newIn = NULL; } Node* ch = p->child(*r); if (ch) { if (*r) { m_p = ch; m_i = m_p->l + 1; return false; } ( (Leaf*)ch)->from.push_back( m_texts.size() ); } else { Leaf* leaf = newLeaf(r, m_text + m_len + 1); p->child(*r) = leaf; leaf->prnt = p; m_leafCnt++; leaf->from.push_back(m_texts.size() ); } if (i < r) { m_p = p->sfxLink; goStr(NULL, NULL); } } return true; } InNode* m_root; vector //the following members are to help the extensions. Node* m_p; InNode* m_newIn; const char *m_i, *m_text; int m_leafCnt, m_len; }; //Test suite and usage example #include int main() { Suffix sa[100]; int lcp[100]; char a[] = "xabxa", b[] = "babxba"; SuffixTree t; t.addText(a); t.addText(b); t.toSuffixArray(sa, lcp); int cnt = strlen(a) + strlen(b); for (int i = 0; i < cnt; i++) { cout << sa[i].from<<" "<< sa[i].str <<" "< return 0; //output: 0 a 0 // 1 a 1 // 0 abxa 1 // 1 abxba 3 // 1 ba 0 // 1 babxba 2 // 0 bxa 1 // 1 bxba 2 // 0 xa 0 // 0 xabxa 2 // 1 xba 1 } 关键词/Tags: acm 后缀数组 后缀树 代码] 用倍增算法构造后缀数组,O(n log n) 我很懒的 @1985-08-05 17:04 本文是这篇文章的附件。 ///////////////////////////////////////////////////////////////// //Constructing Suffix Array with DoublingAlgorithm, O(n log n). ///////////////////////////////////////////////////////////////// #include #include using namespace std; const int MAX_SFX = 210000; struct Sfx { int i; int key[2]; bool operator < (const Sfx& s) const { return key[0] < s.key[0] || key[0] == s.key[0] &&key[1] < s.key[1]; } }; int g_buf[MAX_SFX + 1]; Sfx g_tempSfx[2][MAX_SFX], *g_sa =g_tempSfx[0]; void cSort(Sfx* in, int n, int key, Sfx*out) { int* cnt = g_buf; memset( cnt, 0,sizeof(int) * (n + 1) ); for (int i = 0; i < n; i++) { cnt[ in[i].key[key] ]++; } for(int i = 1; i <= n; i++) { cnt[i] += cnt[i - 1]; } for (int i = n - 1; i >= 0; i--) { out[ --cnt[ in[i].key[key] ] ] = in[i]; } } //Build a suffix array from string 'text'whose length is 'len'. //write the result into global array'g_sa'. void buildSA(char* text, int len) { Sfx *temp = g_tempSfx[1]; int* rank = g_buf; for (int i = 0; i < len; i++) { g_sa[i].i = g_sa[i].key[1] = i; g_sa[i].key[0] = text[i]; } sort(g_sa, g_sa + len); for (int i = 0; i < len; i++) { g_sa[i].key[1] = 0; } int wid = 1; while (wid < len) { rank[ g_sa[0].i ] = 1; for (int i = 1; i < len; i++) { rank[ g_sa[i].i ] = rank[g_sa[i - 1].i ]; if ( g_sa[i-1] < g_sa[i] ) { rank[ g_sa[i].i ]++; } } for (int i = 0; i < len; i++) { g_sa[i].i = i; g_sa[i].key[0] =rank[i]; g_sa[i].key[1] = i + wid cSort(g_sa, len, 1, temp); cSort(temp, len, 0, g_sa); wid *= 2; } } int getLCP(char* a, char* b) { int l=0; while(*a && *b && *a==*b) { l++; a++; b++; } return l; } void getLCP(char* text, Sfx* sfx, int len,int* lcp) { int* rank = g_buf; for (int i=0, r=0; i < len; i++, r++) { rank[ sfx[i].i ] = r; } lcp[0] = 0; if (rank[0]) { lcp[ rank[0] ] = getLCP( text, text + sfx[ rank[0]-1 ].i ); } for (int i = 1; i < len; i++) { if ( !rank[i] ) { continue; } if (lcp[ rank[i - 1] ] <= 1) { lcp[ rank[i] ] = getLCP( text+i, text+sfx[ rank[i]-1 ].i ); } else { int L = lcp[ rank[i - 1] ] - 1; lcp[rank[i]] = L+getLCP(text+i+L, text+sfx[rank[i]-1].i+L); } } } //Test suite and usage example #include using namespace std; int main() { char str[] = "aabbaa{post.content}ababab"; int from[] = {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1}; int lcp[13]; buildSA(str, 13); getLCP(str,g_sa, 13, lcp); for (int i=1; i<13; i++)//The first suffix is useless (empty). {cout< return 0;//output: 0 a 0 // 0 aa 1 // 0 aabbaa 2 // 1 ab 1 // 1 abab 2 // 1 ababab 4 // 0 abbaa 2 // 1 b 0 // 0 baa 1 // 1 bab 2 // 1 babab 3 // 0 bbaa 1 } //used by suffix treenode.