後綴樹的實現(時間複雜度O(m^3))

構建的基本思路:首先往樹中插入最長的後綴即字符串本身,然後依次插入次長的後綴,重複此步驟直到插入最後一個空串;

1)插入串本身;//初始化操作

2)若上一個後綴爲S,令S=aW(W爲下一個後綴)。往樹中插入後綴W,重複本操作直到S=$;

按照定義取做的話,算法的時間複雜度爲O(m^3)

在這裏我們只需要記錄邊集合即可,對於每條邊保存該邊的起始和結束位置,以及此邊保存的字符串信息(起始&結束)

首先找出待插入串S=beta,存在兩種情況。①在樹中已經存在此字段不需要進行任何處理;②樹中存在與之部分匹配的部分

或者不匹配,對於第二種情況的不存在匹配只需要創建一個葉子結點於根結點相連接即可、當對於存在部分匹配的情況時

只需要增加一個內部結點和一個葉結點即可(修改原邊以及增加兩條新邊)。

#include<iostream>
#include<algorithm>
using namespace std;
#define N 10005
typedef struct E{
	int pre;
	int child;
	int start;
	int end;
}E;//邊結構體 
E e[N];
int k=0;//邊的數目 
int find(string s1,string s2){
	int l1=s1.length();
	int l2=s2.length();
	int m = l1>l2?l2:l1;//取最短邊 
	int flag=0;
	for(int i=0;i<m;i++){
		if(s1[i]==s2[i]){
			flag++;
		}
		else{
			break;
		}
	}
	return flag;//匹配長度 
	//時間複雜度O(n) 
}
bool cmp(E a,E b){
	return a.pre<b.pre;//升序 
}
int count=0;
int main(){
	string str;
	cin>>str;//讀取字符串 
	e[0].pre=-1;
	e[0].child=0;
	e[0].start=0;
	e[0].end=str.length(); //初始化結點 
	for(int i=1;i<str.length();i++){
		string beta=str.substr(i);//獲取當前的串
		int root=-1;//從根結點開始遍歷 
		sort(e,e+k,cmp);//類似於快速排序 O(nlogn) 
		for(int j=0;j<=k;j++){ 
			if(e[j].pre==root&&beta[0]==str[e[j].start]){//找到根結點 
				int length=find(str.substr(e[j].start,e[j].end),beta);//匹配成功長度 
				//cout<<"截取字符"<< str.substr(e[j].start,e[j].end)<<endl;
				if(length==beta.length()){
					root=0; 
					break; 
					//不做任何處理 ,如果此串已經匹配成功 
				}
				else if(length<beta.length()){
					//此串要麼在中間匹配中斷,要麼沿着某一路徑繼續匹配; 
					int lt=e[j].end-e[j].start;//當前串的長度 
					root=e[j].child;
					//插入一個內結點和一個葉子結點 
					if(lt>length){
							k++;
							e[j].child=k;
							e[j].end=e[j].start+length;
							e[k].pre=k;
							e[k].child=root;
				    		e[k].start=e[j].end;
							e[k].end=e[k].start+lt-length;
							k++;
							e[k].pre=k-1;
							e[k].child=k;
							e[k].start=i+length;
							e[k].end=i+beta.length();//線性時間複雜度 
							break;
					}
				}
			}
		}
		if(root==-1){
			k++;//創建葉子結點 
			e[k].pre=root;
			e[k].child=k;
			e[k].start=i;
			e[k].end=i+beta.length();
			//未找到匹配字符串 
		}
	}
	//打印輸出後綴樹 
	for(int l=0;l<=k;l++){
		cout<<e[l].pre<<" "<<e[l].child<<" "<<e[l].start<<" "<<e[l].end<<endl;
	}
	cout<<endl;
	return 0;
} 

運行結果:

 

優化後綴樹: (方法)

後綴鏈:若xα表示一個字符串,其中x爲一個單獨的字符,α爲一個子串(可能爲null),則一個路徑標識爲xα的內部結點v而言,

如果存在另一個路徑標識位α的結點s(v),那麼從結點v到s(v)的指針被稱爲後綴鏈。

(通過後綴鏈並不用每次都從根結點開始遍歷,增加程序的執行速度)

命題1:如果有個新的路徑標籤xα爲內結點v在第i+1個階段的第j個擴展中被加入樹中,那麼要麼路徑標籤α的內結點已經在樹

中存在,要麼在第j+1個擴展中會出現;如下圖所示:

證明:S=xα在第I+1次的第j次擴展中加入了樹中(只可能利用規則2進行分裂),則說明xα後面的存在一個字符c不是S【i+1】

因此在第j+1次擴展中此時S=α,一定會有個路徑標籤爲α的路徑,在這裏會產生兩種情況第一種是α後面跟的字符是c,

另一種是不是c的其他字符。對於第一種情況:根據規則2會產生一個內結點s(v);對於第二種情況:s(v)結點顯然存在;

推論:在Ukknonen算法中任何一個剛被創建的結點v到下一個擴展爲止都將有一個後綴鏈接從它出發;

推論:在任何隱含後綴樹中如果存在某個路徑標識的內結點v,則必定存在一個對應結點s(v),其路徑標識爲α;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章