利用二分法求Lw.
(1)直接利用已排列的pos数组进行求解.引入符号u^p表示字符串u的长度为p的前缀,u ≤P v表示字符串u的长度为p的前缀小于v,引入字符串W=w0w1...wp-1(p<n),令
Lw=min(k: W≤P Apos[k] or k=N) and Rw=max(k: Apos[k] ≤P W or k=-1) .
则W和A的某个字串匹配,当且仅当i=pos[k](k[Lw,Rw]).可以用二分法搜索进行计算Lw和Rw,时间复杂度为O(logn),又每次单独的字符比较需要O(P)的时间,所以总的时间复杂度为O(Plog(n)).
(2)为了降低时间复杂度,对(1)的方法进行改进,假定后缀数组的一组排列(L,...,R),M是其中心,则下次搜索将在(L,...,M)中进行,或在(M,...,R)中进行,又知 k=lcp(L,W)。
Case 1:k<lcp(L,M)
说明M的第k+1个字符和L的第k+1个字符相等,因为W>M,所以下次查找在(M,R)中进行;
Case 2:k>lcp(L,M)
说明W<M,所以下次查找在(L,M)中进行;
Case 3:k=lcp(L,M)
说明L,M和W的前k个均相等,要判断下次循环在左半部分还是右半部分,需要比较M和W的第k+1个字符大小,若相等下次查找在(L,M)中进行,否则下次查找在(M,R)中进行。
继续循环直到k=p或L>=R。
由于W的所有字符均只查找了一次,所以总的时间复杂度为O(P+log(n) ).
利用Lcp进行全文二分法搜索
lcp(i,j)若为内部节点则为lcp[(i+j)/2],若为叶结点则为height[j].这里只能求得部分后缀的lcp.在字符串s中查找子字符w只需要O(log(n))时间。
/** 用O(1)的时间得到lcp(i,j) **/
int LCP(int i,int j){
if(i==j-1)return height[j];
return lcp[(i+j)/2];
}
/** 查找s中是否有子串w,若有返回w第一次出现的首字母索引,若无返回0 **/
int searching(char *w,int n,char *s,int l,int r,int k){
if(l>r-1)return -1;//说明S中不含w子串
if(k==P)return Pos[l];//s中含有w子串
int m=(l+r)/2;
while(s[Pos[l]+k]==w[k])//求lcp(l,w)
k++;
if(k==P)return Pos[l];
if(l==r-1&&LCP(l,r)!=k){m=r;return -1;}//只要lcp(r-1,r)!=lcp(r-1,w)就不可能匹配
/** 二分法搜索 **/
int L;
if(k<LCP(l,m))L=searching(w,n,s,m,r,k);//在m+1~r之间
else if(k>LCP(l,m))L=searching(w,n,s,l,m,k);//在l+1~m-1之间
else {
while(w[k]==s[Pos[m]+k]){
k++;
if(k==P)return Pos[m];//与m匹配成功
}
if(l==r-1)return -1;//w和l及m/r都不可能匹配
else if(Pos[m]+k==n||w[k]>s[Pos[m]])L=searching(w,n,s,m,r,k);//在 m+1~r之间
else L=searching(w,n,s,l,m,k);//在l+1~m-1之间
}
printf("L:%d Pos:%d\n",L,Pos[L]);
return L;
}
3.4结果分析
该字符串匹配问题也可用后缀树求解,通过自底向上遍历后缀树取代后缀数组的排序及lcp的计算,方法更为简洁,且在字母表内容很少而且确定时,后缀树的建立只需要O(n)时间,优于后缀数组O(log(n)),但后缀树所需要的空间是后缀数组的3~5倍,且后缀数组的搜索时间为O(P+log(n))与后缀树相近甚至在某些情况下优于后缀树。