前缀树和Trie图
一、前缀树
结构:用树的方式表示字符串,每个节点有p和e,p表示的是有一个字符串经过该结点,e表示的是其是否为一个字符串的终止结点。如图所示:
作用:用于字符串的查找和判断字符串的前缀。
代码:
//结点的结构,有pass和end,还有其孩子结点,也就是边
//注意字符的值是在边上的
struct node
{
int p;
int e;
//这里默认都是小写字母,所以只有26个
//若孩子的情况比较多,那么也可以使用map
node* child[26];
node()
{
p = 0;
e = 0;
memset(child,0,sizeof(child));
}
} ;
//根据输入的字符建立前缀树
void build(string s,node* root)
{
int len = s.length();
//其实root->p就记录了总的有几个字符串
root->p++;
for(int i = 0;i<len;++i)
{
if(root->child[s[i]-'a']==NULL)
{
root->child[s[i]-'a'] = new node();
}
root = root->child[s[i]-'a'];
root->p++;
}
root->e++;
}
//查找某个字符串
bool search(string s,node* root)
{
int len = s.length();
for(int i = 0;i<len;++i)
{
if(root->child[s[i]-'a']==NULL)
return false;
root = root->child[s[i]-'a'];
}
if(root->e>0)
return true;
return false;
//如果是判断是否有以s为前缀的字符串,那么
//不用判断root->e是否大于0,直接返回true就可以了
}
总结:从左到右遍历字符,从上到下遍历树。
二、TRIE图(寻找子串)
作用:进行多模式匹配,也就是给定多个模式串建立Trie图,之后给定母串(被匹配串)。判断母串中是否含有模式串。
结构:首先是根据模式串建立Trie树,之后在trie上的每个节点建立前缀指针,得到trie图,前缀指针的特点是:
- 节点S的前缀指针指向的节点T是S对应字符串的一个子串
- 如果节点T是危险节点(也就是某字符串的终止结点),那么S也是危险节点
注意:
- trie树的节点有前缀指针、孩子、危险标志(标志其是否为某字符串的终止结点)
- 这里建树的时候使用的数组,这对C++没有自动垃圾回收来说比较友好,毕竟使用new的时候delete是一件很头疼的事情
模板题:
题目:给N个模式串,以及M个句子,判断每个句子里是否包含模式串
句子和模式串都由小写字母组成。N,M <= 1000。
每个模式串长度不超过20,每个句子长度不超过1000。
代码:
//trie图结构
struct node
{
node* child[26];
node* prev;
bool isDanger;
node()
{
memset(child,0,sizeof(child));
prev = NULL;
isDanger = false;
}
} tree[20000];
int nodeCount = 2;
//根据输入的字符串建立TRIE树
//root为tree【1】
void build(node* root,string s)
{
int len = s.size();
for(int i = 0;i<len;++i)
{
if(root->child[s[i]-'a']==NULL)
{
nodeCount++;
root->child[s[i]-'a'] = tree+nodeCount;
}
root = root->child[s[i]-'a'];
}
root->isDanger = true;
}
//建立前缀指针得到trie图
//在trie中我们引入了一个虚拟的根结点
//这个节点的所有孩子指向真实的root
//这样做的原因是在字符串匹配的时候,当不匹配的时候,能够使得字符串移到下一个字符
void buidTrie()
{
for(int i = 0;i < 26;++i)
tree->child[i] = tree+1;
tree+1->prev = tree;
tree->prev = NULL;
tree->isDanger = false;
queue<node*> q;
q.push(tree+1);
while(!q.empty())
{
node* cur = q.top();
q.pop();
node* kid;
for(int i = 0;i<26;++i)
{
kid = cur->child[i];
if(kid)
{
node* Prev = cur->prev;
while(Prev->child[i]==NULL)
Prev = Prev->prev;
kid->prev = Prev->child[i];
//如果前缀指针指向的是危险节点,那么本节点也会变成危险节点
kid->isDanger = Prev->child[i]->isDanger;
q.push(kid);
}
}
}
}
//查找
bool search(string s)
{
node* cur = tree+1;
int len = s.size();
for(int i = 0;i<len;++i)
{
//此处体现了假根结点的作用
while(cur->child[s[i]-'a']==NULL)
cur = cur->prev;
cur = cur->child[s[i]-'a'];
if(cur->isDanger)
return true;
}
return false;
}
要和肖战一样努力!!!!!!!!!!!