后缀数组的应用
来源:互联网 发布:fbp算法源代码及注释 编辑:程序博客网 时间:2024/05/17 22:57
本文参考了 后缀数组--处理字符串的有力工具
子串:字符串S 的子串r[i..j],i≤j,表示r 串中从i 到j 这一段,
也就是顺次排列r[i],r[i+1],...,r[j]形成的字符串。
后缀:后缀是指从某个位置i 开始到整个串末尾结束的一个特殊子串。字符串r 的从第i 个字符开始的后缀表示为Suffix(i) , 也就是
Suffix(i)=r[i..len(r)]
后缀数组:后缀数组SA 是一个一维数组,它保存1..n 的某个排列SA[1],
SA[2],……,SA[n],并且保证Suffix(SA[i]) < Suffix(SA[i+1]),1≤i<n。
也就是将S 的n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺
次放入SA 中名次数组:名次数组Rank[i]保存的是Suffix(i)在所有后缀中从小到大排
列的“名次”。
简单的说,后缀数组是“排第几的是谁?”,名次数组是“你排第几?”
用倍增算法:
倍增算法的主要思路是:用倍增的方法对每个字符开始的长度为2k 的子字
符串进行排序,求出排名,即rank 值。k 从0 开始,每次加1,当2k 大于n 以
后,每个字符开始的长度为2k 的子字符串便相当于所有的后缀。并且这些子字
符串都一定已经比较出大小,即rank 值中没有相同的值,那么此时的rank 值就
是最后的结果。每一次排序都利用上次长度为2k-1 的字符串的rank 值,那么长
度为2k 的字符串就可以用两个长度为2k-1 的字符串的排名作为关键字表示,然
后进行基数排序,便得出了长度为2k 的字符串的rank 值。
代码如下:
/*倍增算法 */#define maxn 255int wa[maxn],wb[maxn],wv[maxn],wsn[maxn];/*最后一个为0 这样不会溢出的 哈哈*/int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}//r表示 rank数组void da(int *r,int *sa,int n,int m){//由rank数组生成sa;int i,j,p,*x=wa,*y=wb,*t;for(i=0;i<m;i++)wsn[i]=0;for(i=0;i<n;i++)wsn[x[i]=r[i]]++;for(i=1;i<m;i++)wsn[i]+=wsn[i-1];for(i=n-1;i>=0;i--)sa[--wsn[x[i]]]=i;for(j=1,p=1;p<n;j*=2,m=p){/*对第二个关键字排序 原数组的下标保存在y*/for(p=0,i=n-j;i<n;i++)y[p++]=i;for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;/*对第一个关键字排序*/for(i=0;i<n;i++)wv[i]=x[y[i]];for(i=0;i<m;i++)wsn[i]=0;for(i=0;i<n;i++)wsn[wv[i]]++;for(i=1;i<m;i++)wsn[i]+=wsn[i-1];for(i=n-1;i>=0;i--)sa[--wsn[wv[i]]]=y[i];/*此时y用来保存rank 用sa生成新的rank 保存在x中*/for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;}}void fun_2(char *str,int len)/*生成长度为1的 rank*/{int i,p;int *sa=new int[len];for(i=0;i<maxn;i++){wsn[i]=0;}for(i=0;i<len;i++){sa[i]=0;wsn[str[i]]++;}for(i=1;i<maxn;i++){wsn[i]+=wsn[i-1];}for(i=len-1;i>=0;i--)sa[--wsn[str[i]]]=i;/*for(i=0;i<len;i++){cout<<i<<" sa="<<sa[i]<<endl;}*/int *r=new int[len];for(i=1,r[sa[0]]=0,p=1;i<len;i++){if(str[sa[i]]==str[sa[i-1]])r[sa[i]]=p-1;elser[sa[i]]=p++;}cout<<" hi "<<endl;for(i=0;i<len;i++)cout<<i<<" "<<r[i]<<endl;da(r,sa,len,maxn);//cout<<" len="<<len<<endl;for(i=0;i<len;i++)cout<<" i="<<i<<" "<<sa[i]<<endl;}本算法用到基数排序 先对第二个关键字y 排序,在对第一个关键字y[i]排序 ,x是名次数组
用c++ sort生成后缀数组 代码如下:
void fun(string str)//调用sort生成后缀数组{int len=str.size(),i,j;string *src=new string[len];for(i=0;i<len;i++){src[i]=str.substr(i,len);//生成后缀数组//cout<<i<<" "<<src[i]<<endl;} sort(src,src+len,cmp);cout<<" fun "<<endl;for(i=0;i<len;i++){cout<<len-src[i].length()<<endl;cout<<src[i]<<" "<<endl;}delete []src;}height 数组:定义height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公
共前缀,也就是排名相邻的两个后缀的最长公共前缀。那么对于j 和k,不妨设
rank[j]<rank[k],则有以下性质:
suffix(j) 和suffix(k) 的最长公共前缀为height[rank[j]+1],
height[rank[j]+2], height[rank[j]+3], … ,height[rank[k]]中的最小值。
那么应该如何高效的求出height 值呢?
如果按height[2],height[3],……,height[n]的顺序计算,最坏情况下
时间复杂度为O(n2) 。这样做并没有利用字符串的性质。定义
h[i]=height[rank[i]],也就是suffix(i)和在它前一名的后缀的最长公共前
缀。
h 数组有以下性质:
h[i]≥h[i-1]-1
证明:
设suffix(k)是排在suffix(i-1)前一名的后缀,则它们的最长公共前缀是
h[i-1]。那么suffix(k+1)将排在suffix(i)的前面(这里要求h[i-1]>1,如果
h[i-1]≤1,原式显然成立)并且suffix(k+1)和suffix(i)的最长公共前缀是
可以依次求h[0] h[1] h[n-1] h[i]=height[rank[i]]
代码如下:
struct information{int *height;int *sa;};/*求height数组 h(i)=height[rank[i]] h[i]>=h[i-1]*/struct information fun_height(string str)//调用sort生成后缀数组{int len=str.size(),i,j;information res;string *src=new string[len];for(i=0;i<len;i++){src[i]=str.substr(i,len);//生成后缀数组//cout<<i<<" "<<src[i]<<endl;} sort(src,src+len);/*生成rank数组 sa数组*/int *rank=new int[len];int *sa=new int[len];for(i=0;i<len;i++){rank[len-src[i].length()]=i;//cout<<src[i]<<" "<<endl;}for(i=0;i<len;i++){sa[rank[i]]=i;}/*cout<<" sa "<<endl;for(i=0;i<len;i++){cout<<sa[i]<<endl;}*//*生成heights数组 h[i]=height[rank[i]] 则 h[i]>=h[i-1]-1*/int * height=new int[len];int k=0;for(i=0;i<len;i++){if(rank[i]==0){height[0]=0;k=0;}else{if(k>0) k--;int j=sa[rank[i]-1];while(str[i+k]==str[j+k]) k++;height[rank[i]]=k;}}delete []src;delete []rank;res.height=height;res.sa=sa;return res;}以上出后缀数组和height数组
下面看几个后缀数组的例子:
回文问题:
考虑到回文长度为奇数 偶数的情况,代码如下:
void get_longest_huiwen2(string str){int len=str.size(),i,j,k,max=-0xffff, start;for(i=0;i<len;i++){/*回文长度为偶数的时候*/k=i+1;j=i;int length=0;while(k<len&&j>=0){if(str[k]==str[j]){k++;j--;}elsebreak;length+=2;if(max<length){max=length;start=j+1;}}/*长度为奇数的时候*/k=i+1,j=i-1,length=1;while(k<len&&j>=0){if(str[k]==str[j]){k++;j--;}elsebreak;length+=2;if(max<length){max=length;start=j+1;}}}cout<<"get_longest_huiwen2 max ="<<max <<endl;cout<<str.substr(start,max)<<endl;}用后缀数组的话,回文子串:如果将字符串L 的某个子字符串R 反过来写后和原来的字符串R一样,则称字符串R 是字符串L 的回文子串
代码如下:
/*最长回文子串*/bool isok(int *sa,int i,int len){return (sa[i]<=(len-1)/2-1&&sa[i-1]>=(len+1)/2)||(sa[i-1]<=(len-1)/2-1&&sa[i]>=(len+1)/2);}void get_longest_huiwen(string str2){string s(str2.rbegin(),str2.rend());//逆序下string str=str2+'$'+s;information info=fun_height(str);int len=str.size();int max=-0xffff,i;//最长子回文的长度 其实求的是 height[i] 的最大值 且 sa[i] sa[i-1] 一个在0--->(len-1)/2-1 另一个在 (len+1)/2-->len-1int start,start2;//记下回文的开始位置for(i=1;i<len;i++){//cout<<i<<" "<<info.height[i]<<endl;if(info.height[i]>max&&isok(info.sa,i,len)){//cout<<str.substr(info.sa[i-1],len)<<" "<<str.substr(info.sa[i],len)<<endl;max=info.height[i];start=info.sa[i];start2=info.sa[i-1];if(start2<start)start=start2;}}/*打印回文*/cout<<" max ="<<max<<endl;cout<<str.substr(start,max)<<endl;}子串的个数 问题:
每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相同的前缀的个数。如果所有的后缀按照suffix(sa[1]), suffix(sa[2]),suffix(sa[3]), …… ,suffix(sa[n])的顺序计算,不难发现,对于每一次新加进来的后缀suffix(sa[k]),它将产生n-sa[k]+1 个新的前缀。但是其中有height[k]个是和前面的字符串的前缀是相同的。所以suffix(sa[k])将“贡献”出n-sa[k]+1- height[k]个不同的子串。累加后便是原问题的答案。这个做法
的时间复杂度为O(n)。
代码:
int get_cnt(string str){information info=fun_height(str);int len=str.size();/*对height数组 当加入height[i]的时候,增加的子串的个数为 len-1-sa[i]+1-height[i] len-1下标从0开始的*/int sum=len-1-info.height[0]+1;for(int i=1;i<len;i++){sum+=len-1-info.sa[i]+1-info.height[i];}return sum;}
重复子串:字符串R 在字符串L 中至少出现两次,则称R 是L 的重复子串
算法分析:
这道题是后缀数组的一个简单应用。做法比较简单,只需要求height 数组里的最大值即可。首先求最长重复子串,等价于求两个后缀的最长公共前缀的最大值。因为任意两个后缀的最长公共前缀都是height 数组里某一段的最小值,那么这个值一定不大于height 数组里的最大值。所以最长重复子串的长度就是
height 数组里的最大值。这个做法的时间复杂度为O(n)
代码:
/*取最长重复子串 这个2个子串可以重复*/void get_longest(string str){information info=fun_height(str);int len=str.size();int max=-0xffff,i,index,index2;for(i=0;i<len;i++){if(max<info.height[i]){max=info.height[i];if(max==0){index=info.sa[0];}else{index=info.sa[i-1];index2=info.sa[i];}}}/*打印出结果*/if(max==0) return;else{cout<<"max ="<<max<<endl;cout<<"substr1 ="<<str.substr(index,len)<<endl;cout<<"substr2 ="<<str.substr(index2,len)<<endl;}}不可重叠最长重复子串(pku1743)
给定一个字符串,求最长重复子串,这两个子串不能重叠
这题比上一题稍复杂一点。先二分答案,把题目变成判定性问题:判断是否存在两个长度为k 的子串是相同的,且不重叠。解决这个问题的关键还是利用
height 数组。把排序后的后缀分成若干组,其中每组的后缀之间的height 值都不小于k
/*取最长重复子串 这个2个子串不可以重复*/void get_longest2(string str){information info=fun_height(str);int len=str.size();int k=len/2,i,start,end;int ii,max,min;//height[start]--->height[end] 中最大的sa[]用max 最小的sa[]的用minbool flag=false;//一组的开始标志/*假设最长不重叠的重复子串为k */for(;;k--){/*对 height 按长度k 分组*/for(i=0;i<len;i++){if(!flag&&info.height[i]>=k){flag=true;start=end=i;}else if(flag&&info.height[i]<k){max=-0xffff,min=0xffff;for(ii=start-1;ii<=end;ii++){if(info.sa[ii]>max)max=info.sa[ii];if(info.sa[ii]<min)min=info.sa[ii];if(max-min>=k){cout<<"最大不重复的 "<<k<<endl;cout<<str.substr(min,len)<<endl;cout<<str.substr(max,len)<<endl;return;}flag=false;}}else{end=i;}}}}
还有好的例子,后缀数组的计算和height数组的计算 时间复杂度为 nlogn
- 后缀数组的应用
- 后缀数组的应用
- 后缀数组的应用
- 后缀数组的应用
- 后缀数组的应用
- 后缀数组的例题应用
- hdu 1403 后缀数组的应用
- 后缀数组的应用 hdu 3518
- hdu3518 后缀数组的height应用
- POJ 2774(后缀数组的应用)
- HDU5769后缀数组的简单应用
- 后缀数组及应用
- 后缀数组及其应用
- 后缀数组应用
- 后缀数组应用
- 后缀数组应用小结
- POJ 3261 浅谈后缀数组HEIGHT数组的实际应用
- 后缀数组的应用:最长重复(or回文)子串
- iframe安全威胁
- @import 对于网站的性能有某些负面…
- jade Language Reference
- CKEDITOR使用与配置
- 汇编 段内转移和段间转移
- 后缀数组的应用
- 查询前10条数据oracle
- Unbalanced calls to begin/end appearance transitions for <IDOOrderHomeViewController: 0x8da2960>.
- ios 打开图片库和相机选择图片界面修改为简体中文
- iOS: FFmpeg编译和使用问题总结
- 图像变换-霍夫变换
- Swift语言编程入门实战系列教程(三)---一颗爱你的心
- 6-10复习
- 批处理新手入门导读