后缀树:Trie树只适合前缀匹配和全字匹配,并不适合后缀和子串匹配。而后缀树(Suffix Tree)在这方面则非常适合。它与Trie树最大不同在于,后缀树的单词集合是由指定字符串的后缀子串构成。
eg: BIBS. 构造的后缀树如右图所示: 对于每个结点,包含两个数据成员,该结点出发可以到达的其他结点的指针(map)、从根结点到子孙结点的路径构成的子串在源字符串中的开始位置(vector)。 map<char,结点指针>、vector<int> 右图中包含4个子串,BIBS,BS,IBS,S。 BIBS中vector有两个数据0和2,因为BIBS和BS在源字符串中的位置是0和2.char==B,map有两个数据成员,一个为I和指向I的指针,另一个为S和指向S的指针。 |
|
#include<iostream>
#include<vector>
#include<map>
#include<string>
using namespace std;
class SuffixTreeNode //后缀树的结点类
{
public:
SuffixTreeNode() { }
void insertString(string s,int idx); //往后缀树中插入一个字符串
vector<int> getIndex(string s); //获取字符串在源字符串中出现的位置
~SuffixTreeNode();
private:
map<char,SuffixTreeNode*> subTreeNode; //结点出发可以到达的下一个字符以及指向该结点的指针
vector<int> index; //存放子字符串在S中的起始位置
//可能有多个子串,或者从一个结点出发可以找到多个不同子串
};
void SuffixTreeNode::insertString(string s,int idx)
{//建立后缀树
index.push_back(idx); //字符串的起始位置保存.放到这里的原因,从root结点开始,等到字后一个字符的时候Str已被耗尽,也就是map没有元素了,但是这个结点index却要保存
if(s.length()>0) //如果字符串还没有耗尽,递归接着向下构造后缀树
{
char value = s[0]; //结点value的值解释字符串的第一个字符
//判断该字符在不在该结点的map中,在就继续向下插入下一个字符,不在就新建一个结点并在当前结点的map中保存相关信息
SuffixTreeNode* next = NULL;
if(subTreeNode.find(value)!=subTreeNode.end()) //如果结点中存在该字符,next指针指向该结点
{
next = subTreeNode.find(value)->second;
}
else //没有该字符,就新建一个结点,并在当前结点的map中保存这个字符值,以及到这个字符的指针
{
SuffixTreeNode* node = new SuffixTreeNode;
subTreeNode.insert(::make_pair(value,node));
next = node;
}
//递归利用字符串中的下一个字符接着构造后缀树
string subStr = s.substr(1);
next->insertString(subStr,idx); //这个子串的起始位置是idx,以后一直是这个,不会变
}
}
vector<int> SuffixTreeNode::getIndex(string s)
{
//挨个字符匹配,最后返回最后一个字符所指向的结点的vector
if(""==s) //成功匹配到最后返回该结点的index
return index;
else
{
if(subTreeNode.find(s[0])!=subTreeNode.end())
return ((subTreeNode.find(s[0]))->second)->getIndex(s.substr(1)); //接着向下匹配
else
{//找不到,直接返回空
return vector<int>(0);
}
}
}
SuffixTreeNode::~SuffixTreeNode()
{//挨个释放动态申请的结点内存
for(auto it = subTreeNode.begin();it!=subTreeNode.end();++it)
{
delete it->second; //这里是一个递归过程
it->second = NULL;
}
}
|
class SuffixTree
{
public:
SuffixTree(string str); //用字符串(源串)初始化一个后缀树
vector<int> getIndex(string str); //获取一个字符串在源串中出现的位置
~SuffixTree();
private:
SuffixTreeNode* _root;
};
SuffixTree::SuffixTree(string str)
{
_root = new SuffixTreeNode();
for(int idx=0;idx!=str.length();++idx)
{
string subStr = str.substr(idx);
_root->insertString(subStr,idx);
}
}
vector<int> SuffixTree::getIndex(string str)
{
return _root->getIndex(str);
}
SuffixTree::~SuffixTree()
{
delete _root;
_root = NULL;
}
int main(int argc,char** argv)
{
string testString = "mississippi";
string arr[4] = {"is","sip","hi","sis"};
SuffixTree tree(testString);
for(int idx=0;idx!=4;++idx)
{
vector<int> ans = tree.getIndex(arr[idx]);
cout<<arr[idx]<<" ";
if(0!=ans.size())
{
for(int iidx=0;iidx!=ans.size();++iidx)
{
cout<<ans[iidx]<<" ";
}
cout<<endl;
}
else
{
cout<<"not find!"<<endl;
}
}
system("pause");
}
|
查询效率:很显然,在上面的算法中,匹配成功正好比较了len(查找单词长度)次字符,则查询效率为O(len).
后缀树还可以用来找出字符串S的最长重复子串S1(比如abcdabcefda里abc同da都重复出现,而最长重复子串是abc,利用vector大小和字符串长度就O(len)就可以找到)、找出字符串S1和S2的最长公共子串(比如字符串acdfg同akdfc的最长公共子串df)、找出字符串S的最长回文串S1(XMADAMYX的最长回文子串是MADAM)。