后缀树创建的算法比较复杂,不是很好懂,个人找到了一个比较好理解的方法。下面一步一步构建一棵后缀树。以字符串 abcabxabcd 为例。
以下各图中,灰色圆表示根节点;红色表示终结,它上面的一个节点是叶子;无色表示一般节点;从根节点到叶子节点构成了当前插入的字符串的后缀。
1.基本方法
_______________________________________________________________________________
插入:a
使用的原则:
原则1:每插入一个字符,都开辟一个(根节点 -> 节点 -> 终结)的结构。
插入 a 时,开辟下面的结构:
因为之前的后缀树是空树,所以直接将上面的结构作为后缀树的初始化。
_______________________________________________________________________________
插入:b
使用的原则:
原则2:将插入的字符追加到所有终结节点的父节点的字符串值的末尾。如果父节点还有其它分支,则在终结节点与父节点之间再创建一个节点,并把字符串设为该字符(如图左边一条分支的节点,原来值为 a ,现在为 ab)
原则1:每插入一个字符,都开辟一个(根节点 -> 节点 -> 终结)的结构。(如图右边一条分支)
这里,引入一个概念,如果终结节点的父节点没有其它分支,则这个父节点是一个叶子节点。如此时,ab, b 都是叶子节点
_______________________________________________________________________________
插入:c
使用的原则:
原则2:不再缀述;
原则1:不再缀述;
插入:a
使用的原则:
原则2:不再缀述;
原则1:不再缀述;
原则3:每次插入一个字符时,都检查新生成的(根节点 -> 节点 -> 终结)结构(如果有的话),在根节点的直接子节点中找到字符串值以该字符开始的那个节点(如果有的话,只可能有一个),将它们合并。左图展示了新增的 a 节点与根节点的子节点 abca 可以合并,合并成为右图,右图即为插入到该字符时的后缀树。
_______________________________________________________________________________
插入:b
使用的原则:
原则2:不再缀述;左边的 a 节点与终结节点之间会产生一个节点,字符串值为 b。
原则1:不再缀述;
原则3:不再缀述;
原则4:插入字符时,查看所有凭空产生的节点(原则1或原则2中产生的节点),与其兄弟节点能不能合并(与原则3里面的合并是一样的处理逻辑),若能合并则合并(如果兄弟节点存在合并,只可能有一个兄弟节点能合并),不能合并则跳过。
如下图中的 蓝色的 b 是凭空产生的节点,它与兄弟节点 bcab 可以合并。
___________________________________________________________________________________
插入:x
使用的原则:
原则2:不再缀述;
原则1:不再缀述;
___________________________________________________________________________________
插入:a
使用的原则:
原则2:不再缀述;
原则1:不再缀述;
原则3:不再缀述;
___________________________________________________________________________________
插入:b
使用的原则:
原则2:不再缀述;
原则1:不再缀述;
原则3:不再缀述;
原则4:不再缀述;
___________________________________________________________________________________
插入:c
使用的原则:
原则2:不再缀述;
原则1:不再缀述;
原则3:不再缀述;
原则4:不再缀述;
___________________________________________________________________________________
插入:d
使用的原则:
原则2:不再缀述;
原则1:不再缀述;
至此,一棵后缀树便构建好了。逻辑其实是非常简单的,我们只需要原则1,原则2,原则4即可构建正确任何字符串的后缀树。
我们来分析一下时间复杂度。
首先,遍历字符串,时间复杂度为 n。
再者,看原则1,2,4是怎样的情况。
原则1:每插入一个字符,都开辟一个(根节点 -> 节点 -> 终结)的结构。
时间复杂度为常数。不必多说
原则2:将插入的字符追加到所有终结节点的父节点的字符串值的末尾。
看似在整个构建过程中这个操作的时间复杂度为: 1 + 2 + 3 + ... + (n - 1) = O(n*n),(插入第 k 个字符需要更新 k-1 个叶子节点)。
可能你想到了一个优化的方案,如果我们把每个叶子节点表示的字符串表示为一个整数 x ,表示该节点的字符串值在原字符串中的索引,这样,就可以省去每次往叶子节点字符串末尾加入字符的操作,因为任意时刻,我们永远只需要用一个整数就可以知道该节点的字符串值是多少。等等,还有一个问题,对于非叶子节点怎么处理?我们这时就要保存两个整数 x, y ,分别表示该节点的字符串值在原字符串中的开始索引和结束索引。因此,我们可以统一为:每个节点都用两个整数 x , y 来表示字符串值,如果 y = -1 则说明这个节点的字符串值是原字符串的从 x 到末尾的子字符串,如果 y != -1 ,则说明是从 x 到 y 的子字符串,这样便可以节省拷贝字符串的空间。而且对于叶子节点,我们不需要更新它的 y,因为它的 y 一直都是 -1。因此,字符串的改变时间可以忽略不讲。
原则4:插入字符时,查看所有凭空产生的节点(原则1或原则2中产生的节点),与其兄弟节点能不能合并(与原则3里面的合并是一样的处理逻辑),若能合并则合并(如果兄弟节点存在合并,只可能有一个兄弟节点能合并),不能合并则跳过。
我们需要一个结构来保存每吸入一个字符后,所有凭空产生的节点。原则四的复杂度关键在于,插入一个字符,会有多少个“凭空产生的节点”。很不幸,最坏的情况下,会产生 n 个,这个 n 是已插入的字符的数量。为什么呢?
我们先引入一个概念,把可能产生合并的节点称为 hasEmptySon 节点。
这两棵后缀树,只要再吸入一个字符 a ,则会产生合并(两个绿色的节点的值变成 aa,其左边兄弟分支将凭空多出一个 a 节点。此时将产生合并)。我们把上图中的紫色的节点称为 hasEmptySon 节点,从字面上理解,即有空的子节点的节点,如上图,hasEmptySon 左边的分支和中间的分支是空,上面没有节点。很明显,凭空产生的节点的父节点是 hasEmptySon 节点。得到结论,每次后缀树吸入一个字符,只需要判断 hasEmptySon有没有子节点的字符串是以该字符开始,即可得知是否需要合并。
两棵树吸入 a 之后到合并的图如下所示:
两棵后缀树合并如下图所示:
很明显, hasEmptySon 节点不可能超过已有的节点。我们只需要证明所有节点都是 hasEmptySon 节点即可。我们举例说明,当字符串为 "bbbbbbb" 时,逐个画出的后缀树依次为:
很明显看出,每吸入一个字符, 除最后一个叶子节点和根节点外,其它所有节点均是 hasEmptySon 节点。
即,最坏的情况下,每吸入一个字符,当前的 hasEmptySon 节点是 n - 1 个。
而算法的核心在于,对于每吸入的字符,比较这个字符与所有的 hasEmptySon 的每个子节点(这里最少的时间复杂度是线性的,考虑用哈希存储子节点的首字符与节点地址的对应),看是否有与某个子节点的首字符相等,相等则合并,不相等则创建新节点作为 hasEmptySon 的子节点。
因此,时间复杂度为 0 + 1 + 2 + ... + (n - 1) = O(n * n)。
因此,本算法的时间复杂度为二次方。
最终给出我实现的代码。
#ifndef __SUFFIX_TREE_IMPL
#define __SUFFIX_TREE_IMPL
#include
#include
#include
using std::vector;
using std::set;
class SuffixTreeImpl;
class TreeNode
{
private:
friend class SuffixTreeImpl;
static const int ROOT_BEGIN = -1;
static const int TO_END = -1;
static const int FIND_NO_SON_START_WITH_CH = -1;
const char *__string;
int __begin,__end; //开始,结束
TreeNode *__parent;
vector
__sons;
bool __hasCurCRoundRemainEmptySon;
bool __dealing;
bool __needToDelete;
public:
TreeNode(const char *str,int begin);
bool isRootNode();
~TreeNode();
char firstCh();
TreeNode* parent();
void addSon(TreeNode *node);
int sonCount();
void setParent(TreeNode *parent);
void setBegin(int beginPos);
int getBegin();
int getEnd();
void setEnd(int endPos);
void print(int spaceCount);
int findSonStartWith(char ch);
void combineWithSonsStartWith(int curPosset,set
& nextHasEmptySonNodes); void upMoveCombine(TreeNode* combineNode,TreeNode* targetNode,int curPos,set
& nextHasSonEmptyNodes); int stringLength(int curPos); void addSons(vector
* node ); vector
* getSons(); void removeSon(TreeNode* node); void removeAllSons(); void hasCurRoundRemainEmptySonNode(bool hasCurCRoundRemainEmptySon); bool hasCurRoundRemainEmptySonNode(); bool markDelete(); void markDelete(bool delete_); bool atDealing(); void atDealing(bool dealing); }; class SuffixTreeImpl { private: const char *__string; TreeNode *__suffixTree; set
__nodesHasEmptySon; public: SuffixTreeImpl(const char *str); virtual ~SuffixTreeImpl(); void create(); void print(); private: void addChar(int curPos); }; #endif #include "SuffixTreeImpl.h" SuffixTreeImpl::SuffixTreeImpl(const char *str) { __string = str; } SuffixTreeImpl::~SuffixTreeImpl() { } void SuffixTreeImpl::create() { __suffixTree = new TreeNode(__string,TreeNode::ROOT_BEGIN); __suffixTree->hasCurRoundRemainEmptySonNode(true); //根节点永远是 hasEmptySon 节点 __suffixTree->atDealing(true); __nodesHasEmptySon.insert(__nodesHasEmptySon.begin(),__suffixTree); int nlength = strlen(__string); for (int i = 0;i < nlength;++ i) { addChar(i); } } void SuffixTreeImpl:: addChar(int curPos) { set
newEmptyNodes; TreeNode *curNode; set
::iterator it = __nodesHasEmptySon.begin(); for (;it != __nodesHasEmptySon.end();) { curNode = (TreeNode*)(*it); if(curNode->markDelete()) //如果 combineWithSonsStartWith 递归处理已经标记了后面将要处理的节点要被删除,此时就应该删除 { delete curNode; it = __nodesHasEmptySon.erase(it); } else { if(curNode->hasCurRoundRemainEmptySonNode()) { curNode->combineWithSonsStartWith(curPos,newEmptyNodes); if(!curNode->isRootNode()) { it = __nodesHasEmptySon.erase(it); } else { curNode->hasCurRoundRemainEmptySonNode(true); //根节点下一轮又是 hasEmptySon 节点 ++ it; } } else { it = __nodesHasEmptySon.erase(it); } if(!curNode->isRootNode())curNode->atDealing(false); } } it = newEmptyNodes.begin(); for (;it != newEmptyNodes.end();++ it) { ((TreeNode*)(*it))->hasCurRoundRemainEmptySonNode(true); ((TreeNode*)(*it))->atDealing(true); //下一轮 __nodesHasEmptySon.insert(*it); } } void SuffixTreeImpl:: print() { __suffixTree->print(0); } bool TreeNode::isRootNode() { return ROOT_BEGIN == __begin; } TreeNode::TreeNode( const char *str,int begin ) { __string = str; __begin = begin; __end = TO_END; __hasCurCRoundRemainEmptySon = false; __needToDelete = false; __dealing = false; } TreeNode::~TreeNode() { vector
::iterator it = __sons.begin(); for (;it != __sons.end();++ it) { delete (*it); } } char TreeNode::firstCh() { return *(__string + __begin); } void TreeNode::addSon( TreeNode *node ) { __sons.insert(__sons.end(),node); node->setParent(this); } void TreeNode::addSons(vector
* node ) { __sons.insert(__sons.end(),node->begin(),node->end()); vector
::iterator it = node->begin(); for (;it != node->end();++ it) { (*it)->setParent(this); } } int TreeNode::sonCount() { return __sons.size(); } void TreeNode::setParent( TreeNode *parent ) { __parent = parent; } void TreeNode::setBegin( int beginPos ) { __begin = beginPos; } int TreeNode::getBegin() { return __begin; } void TreeNode::setEnd( int endPos ) { __end = endPos; } int TreeNode::getEnd() { return __end; } void TreeNode::print( int spaceCount ) { for (int i = 0;i < spaceCount;++ i) { printf(" "); } if(__begin < strlen(__string)) { int lastPos = (TO_END == __end ? strlen(__string) - 1 : __end); for (int i = __begin;i <= lastPos;++ i) { printf("%c",*(__string + i)); } } printf("\r\n"); vector
::iterator it = __sons.begin(); for (;it != __sons.end();++ it) { (*it)->print(spaceCount + 4); } } int TreeNode::findSonStartWith( char ch ) { vector
::iterator it = __sons.begin(); for (;it != __sons.end();++ it) { if(ch == (*it)->firstCh()) { return it - __sons.begin(); } } return FIND_NO_SON_START_WITH_CH; } void TreeNode::combineWithSonsStartWith( int curPos ,set
& nextHasSonEmptyNodes) { this->hasCurRoundRemainEmptySonNode(false); int offset = findSonStartWith(*(__string + curPos)); if(FIND_NO_SON_START_WITH_CH != offset) { vector
::iterator it = __sons.begin() + offset; TreeNode *foundSonNode = *it; if(foundSonNode->hasCurRoundRemainEmptySonNode()) { foundSonNode->combineWithSonsStartWith(curPos,nextHasSonEmptyNodes); this->combineWithSonsStartWith(curPos,nextHasSonEmptyNodes); } else { if(1 == sonCount()) { TreeNode *combineNode; if(this->isRootNode()) { combineNode = new TreeNode(__string,foundSonNode->getBegin()); this->removeSon(foundSonNode); this->addSon(combineNode); combineNode->addSon(foundSonNode); } else { combineNode = this; } upMoveCombine(combineNode,foundSonNode,curPos,nextHasSonEmptyNodes); } else { TreeNode *combineNode = new TreeNode(__string,foundSonNode->getBegin()); this->removeSon(foundSonNode); this->addSon(combineNode); combineNode->addSon(foundSonNode); upMoveCombine(combineNode,foundSonNode,curPos,nextHasSonEmptyNodes); } } } else { TreeNode* newLeafNode = new TreeNode(__string,curPos); newLeafNode->setParent(this); addSon(newLeafNode); } } void TreeNode::upMoveCombine(TreeNode* combineNode,TreeNode* targetNode,int curPos,set
& nextHasSonEmptyNodes) { if(1 == targetNode->stringLength(curPos)) //吸干 { targetNode->parent()->removeSon(targetNode); combineNode->addSons(targetNode->getSons()); combineNode->setEnd(targetNode->getBegin()); targetNode->removeAllSons(); if(targetNode->atDealing()) { targetNode->markDelete(); } else { delete targetNode; } } else { combineNode->setEnd(targetNode->getBegin()); targetNode->setBegin(targetNode->getBegin() + 1); } nextHasSonEmptyNodes.insert(combineNode); } int TreeNode::stringLength( int curPos ) { if(TO_END == __end) { return curPos - __begin + 1; } else { return __end - __begin + 1; } } vector
* TreeNode::getSons() { return &__sons; } void TreeNode::removeAllSons() { __sons.clear(); } TreeNode* TreeNode::parent() { return __parent; } void TreeNode::hasCurRoundRemainEmptySonNode( bool hasCurCRoundRemainEmptySon ) { __hasCurCRoundRemainEmptySon = hasCurCRoundRemainEmptySon; } bool TreeNode::hasCurRoundRemainEmptySonNode() { return __hasCurCRoundRemainEmptySon; } void TreeNode::removeSon( TreeNode* node ) { vector
::iterator it = __sons.begin(); for (;it != __sons.end();) { if(*it == node) { it = __sons.erase(it); } else { ++ it; } } } bool TreeNode::markDelete() { return __needToDelete; } void TreeNode::markDelete( bool delete_ ) { __needToDelete = delete_; } bool TreeNode::atDealing() { return __dealing; } void TreeNode::atDealing( bool dealing ) { __dealing = dealing; }