后缀树
来源:互联网 发布:网站制作推广软件 编辑:程序博客网 时间:2024/05/17 09:32
后缀树
简介:后缀树,就是把一串字符的所有后缀保存并且压缩的字典树。相对于字典树来说,后缀树并不是针对大量字符串的,而是针对一个或几个字符串来解决问题,
比如字符串的回文子串,两个字符串的最长公共子串等等,后面应用会说。
性质:一个字符串构造了一棵树,树中保存了该字符串所有的后缀。
操作:就是建立和应用。
1.建立后缀树
比如单词banana,它的所有后缀显示到下面的。1代表从第一个字符为起点,终点不用说都是字符串的末尾。
以上面的后缀,我们建立一颗后缀树。如下图,为了方便看到后缀,我没有合并相同的前缀
(3)
基于之前前缀树的学习,可写出插入后缀树代码的编写
void SInsert(TrieNode *root , char *word){//后缀树插入int length =strlen(word);for(int i=0;i<length;i++){char *a=word+i; Insert(root, a); }}void Insert(TrieNode *root , char *word) { TrieNode *location=root; while(*word){ if(location->next[*word-'a']==NULL){ TrieNode *newNode=new TrieNode(); newNode->isStr=false; memset(newNode->next,NULL,sizeof(newNode->next));//初始化 location->next[*word-'a']=newNode; } location=location->next[*word-'a'];//location指向字符串在前缀树中下一个位置 word++; //当前字符在字符串中位置 } //字符串已经全部添加到前缀树 //标识前缀树到该节点位置为完整字符串 location->isStr=true;}
前面简介的时候我们说了,后缀树是把一个字符串所有后缀压缩并保存的字典树。
压缩一会再说,简介里面说了是字典树,所以我们把字符串的所有后缀还是按照字典树的规则建立,就成了上图(3)的样子。
注意还是和字典树一样,根节点必须为空。
下面说下更加节省空间的方案,也就是上面提到的压缩。
(4)
因为有些后缀串可能是单串,并不和其他的共用同一个前缀。
比如图(4)的banana这个后缀串,直接可以用1来表示起点,终点是默认的。
图(4)的a节点后面有两个节点标记3和5是右边字符数组的下标,对应着a->3-7,a->5-7。因为a是共有的前缀。
2.重点说下后缀树的应用,它能解决大多数字符串的问题
<1.查找某个字符串s1是否在另外一个字符串s2中
这个很简单,如果s1在字符串s2中,那么s1必定是s2中某个后缀串的前缀。
理解以下后缀串的前缀这个词,其实每个后缀串也就是起始地点不同而已,前缀也就是从开头开始结尾不定。
后缀串的前缀就可以组合成该原先字符串的任意子串了。
比如banana,anan是anana这个后缀串的前缀。
代码
bool qianzhuiSearch(TrieNode *root , char *word) { int length =strlen(word); TrieNode *location=root; for(int i=0;i<length;i++){ if(location->next[*word-'a']==NULL){return 0;}else { location=location->next[*word-'a']; word++;}} return 1;}
<2.指定字符串s1在字符串s2中重复的次数
看图(3),比如说banana是s1,an是s2,那么计算an出现的次数实际上就是看an是几个后缀串的前缀。
上图的a节点是保存所有起始为a字母的后缀串,我们看a字母后的n字母的引用计数即可。
核心代码
int ncount(TrieNode *location, int &count){if(location->isStr==true){ //如果这个点是一个后缀字符串,count++count++;}for(int i=0;i<26;i++){ if(location->next[i]!=NULL){ TrieNode *now=location; //定义一个now是此代码核心,不能直接用location,因为是它是指针,会影响后续循环递归的过程 now=now->next[i]; count=ncount(now, count); //递归}}return count;}int countqianzhuiSearch(TrieNode *root , char *word) { int length =strlen(word); TrieNode *location=root; for(int i=0;i<length;i++){ //找到以“需要计数的字符串”为头后缀树,然后以此向后查找后缀树数,就是重复数 if(location->next[*word-'a']==NULL){return 0;}else { location=location->next[*word-'a']; word++;}}int count=0;TrieNode *now=location; count=ncount(now, count);return count;}在此,列出完整代码
#include<iostream>#include<string>using namespace std;#define MAX 26 //字符集大小class TrieNode{ public: bool isStr; //标识是否是一个完整的字符串 TrieNode *next[MAX]; }; /*插入*/ void Insert(TrieNode *root , char *word) { TrieNode *location=root; while(*word){ if(location->next[*word-'a']==NULL){ TrieNode *newNode=new TrieNode(); newNode->isStr=false; memset(newNode->next,NULL,sizeof(newNode->next));//初始化 location->next[*word-'a']=newNode; } location=location->next[*word-'a'];//location指向字符串在前缀树中下一个位置 word++; //当前字符在字符串中位置 } //字符串已经全部添加到前缀树 //标识前缀树到该节点位置为完整字符串 location->isStr=true;} //查找 bool Search(TrieNode *root , char *word) { TrieNode *location=root; while(*word&&location!=NULL){ location=location->next[*word-'a']; word++; } return(location!=NULL&&location->isStr );}//前缀查找bool qianzhuiSearch(TrieNode *root , char *word) { int length =strlen(word); TrieNode *location=root; for(int i=0;i<length;i++){ if(location->next[*word-'a']==NULL){return 0;}else { location=location->next[*word-'a']; word++;}} return 1;}int ncount(TrieNode *location, int &count){if(location->isStr==true){ //如果这个点是一个后缀字符串,count++count++;}for(int i=0;i<26;i++){ if(location->next[i]!=NULL){ TrieNode *now=location; //定义一个now是此代码核心,不能直接用location,因为是它是指针,会影响后续循环递归的过程 now=now->next[i]; count=ncount(now, count); //递归}}return count;}int countqianzhuiSearch(TrieNode *root , char *word) { int length =strlen(word); TrieNode *location=root; for(int i=0;i<length;i++){ //找到以“需要计数的字符串”为头后缀树,然后以此向后查找后缀树数,就是重复数 if(location->next[*word-'a']==NULL){return 0;}else { location=location->next[*word-'a']; word++;}}int count=0;TrieNode *now=location; count=ncount(now, count);return count;}void Delete(TrieNode *location){ for(int i=0;i<MAX;i++){ if(location->next[i]!=NULL){ Delete(location->next[i]); } } delete location; }
void SInsert(TrieNode *root , char *word){//后缀树插入int length =strlen(word);for(int i=0;i<length;i++){char *a=word+i; Insert(root, a); }}int main(){ //初始化前缀树的根节点,注意这里结构体指针的初始化 TrieNode *root=new TrieNode(); root->isStr =false; //前缀树中每一个节点的下一个节点,分配空间,注意memset的使用 memset(root->next,NULL,sizeof(root->next)); SInsert(root,"banananana");//后缀树插入 //Insert(root,"bcd"); //Insert(root,"xyz"); //Insert(root,"abcdef"); //if(qianzhuiSearch(root,"bcf"))//用来查找某个字符串s1是否在另外一个字符串s2 // cout<<"exist"<<endl; //else // cout<<"no exist"<<endl;int z=countqianzhuiSearch(root,"ban");cout<<z; Delete(root);}
先说下广义后缀树,前面说了后缀树可以存储一个或多个字符串,当存储的字符串数量大于等于2时就叫做广义后缀树。
<3.两个字符串S1,S2的最长公共部分(广义后缀树)建立一棵广义后缀树,如下图(5)
(5)
$和#是为了区分字符串的。
(上面的程序不能实现这个代码,因为isStr是bool型,若想实现,改成int型即可,第一个字符串标识为1,第二个字符串标识为2等)
我们为每个后缀串末尾单独添加一个空间存储区分字符串的符号。
那么怎么找s1和s2串最长的公共部分?
遍历每个后缀串,如果其引用计数为1则直接跳过,因为不可能有两个子串存放在这里,当引用计数>1时,往下遍历,直到分叉分别记录子串的符号,
如果不同,说明他们是不同字符串的,记录已经匹配的值即可,若相同继续下一次遍历。
上图的ana部分,到ana时,子串$结束,然后继续向下,子串anab以#结束,那么匹配了ana。
<4.最长回文串(广义后缀树)
把要求的最长回文串的字符串s1和它的反向(逆)字符串s2建立一棵广义后缀树。
回文串有一个定义就是正反相同,也就是正着和反着可以重和在一起,那么我们直接看这棵广义后缀树的共同前缀即可,每个banana的子串和ananab的子串重合的部分
都是回文串,我们只需要找到最长的即可。比如上面的anana,从后面不同的标记可以看出两个字符串的某个后缀都有这个前缀,能完美重合到一起。即它是回文串。
记录Max,每次找到一个回文串比较即可。
- 后缀树/后缀数组
- 后缀树 后缀数组
- 【后缀自动机】自动机<->后缀树<->后缀数组
- 后缀树
- 后缀树...
- 后缀树
- 后缀树
- 后缀树
- 后缀树
- 后缀树
- 后缀树
- 后缀树
- 后缀树
- 后缀树
- 后缀树。。。。。
- 后缀树
- 后缀树
- 后缀树
- 关于packages.config问题 未声明“packages”元素
- 串口通信
- c语言简易计算器
- C#高级编程:泛型优点和特性
- .NET Standard 2.0 特性介绍和使用指南
- 后缀树
- 剑指offer——二叉树的深度
- pari的用法(stl)
- 认证机构业务管理信息系统、项目流程管理软件
- 不借助第三个变量交换两个整数
- 【每周论文】Graphene: Packing and Dependency-aware Scheduling for Data-Parallel Clusters(OSDI 2016)
- Linux功耗管理(4)_Power Management Interface
- Java笔记:数组
- @Scheduled Spring定时任务每次执行两次解决方案