鍵樹的c++代碼實現

建樹時使用的是Ukkonens算法.因爲看的都是英文資料,所以很喫力。加上這個算法很複雜,偶看了好幾天。主要是在後綴鏈上老是出問題。
對於後綴鏈,我覺得有幾點比較重要,可以幫助你理解建樹的過程。總結如下:

(1)suffix link只能在內部節點之間出現.也就說只能從內部節點指向內部節點.
所以如果節點是葉子的話,就不用考慮了。
(2)一個新的內部節點總是由規則2產生的。規則2要拆分節點。
(3)一個新的內部節點產生的時候,要麼他的suffix link已經存在,要麼suffix link將會在下一次擴展時出現(可能是新創建的,也可能是添加葉子時的那個內部節點,只有這兩種情況)。這裏的下一次擴展是指如果當前這一次是搜索S[j-1,...,i],並添加字符S[i+1],那麼下一次擴展是搜索S[j,...,i],並添加字符S[i+1].

第(3)條比較長,不過很重要。
其他的細節可以拿着資料一遍一遍的看。
有興趣的朋友,也可以聯繫我,我可以推薦幾篇我自己認爲對我幫助比較大的文章給你。

這個程序我用隨機產生的字符串測試,還沒有發現問題。
改天有時間寫一個應用上來演示一下後綴樹的應用。
用後綴樹來求解問題也是件麻煩的事情。

水平有限,難免有錯誤之處。歡迎大家批評指正。

 

#include "stdafx.h"
#include 
#include 
#include 
#include 
#include 
using namespace std;


#define RULE1 4
#define RULE2 5
#define RULE3 6 
#define NORULE 7


//#define  DEBUG

/*
*keyword tree node
*we use the structure left child-right sibling
*to store the tree's nodes
*/
class knode
{
	public:	
		knode()
		{		
			child=NULL;
			sibling=NULL;
			parent=NULL;
			sufflink=NULL;

		//	strNo=0;
			strIndex=0;
			charfrom=0;
			charend=0;		

			//leftC=0;
			//leftDiverse=false;
		}

	//在葉子節點中保存第strNo個字符串,第strIndex位置
//	int strNo;
	int strIndex;
	
	//存儲的字符串用索引表示從charfrom開始,到charend結束.
	int charfrom;
	int charend;

	knode * child;/*left pointer pointing to child*/
	knode * sibling;/*left pointer pointing to sibling*/
	knode * parent;/*parent node*/
	knode* sufflink;/*suffix link in suffix tree*/
	
};


knode * groot;//打印樹的時候用到
char* strdollar=NULL;

/*
搜索結束之有三種情況:
(1)在內部節點上結束.給這個內部節點加一個葉子.
(2)在葉子上結束.直接將字符加到葉子上.
(3)在兩個節點之間結束.此時需要拆分節點.此時offset爲節點上搜索時匹配字符的偏移,前面兩種情況偏移都爲1.
*/

knode * searchNode(knode* curNode,int restStart,int end,int& offset,const char* str)
{	
    offset=0;
    knode * startNode=curNode;
    while (restStart <= end) //等於時只搜索一個字符
    {
        if (startNode->child != NULL)
        {
            startNode=startNode->child;
        }
        //搜索所有的孩子
        while(str[startNode->charfrom] != str[restStart])
        {
            if (startNode->sibling != NULL)
            {
                startNode=startNode->sibling;
            }
            else
            {
                //在一個內部節點的孩子上搜索失敗,返回這個內部節點.要在這個內部節點上添加葉子.
                offset=1;
                return startNode;
            }
        }	
        //Speedup trick 1:跳過此節點的剩餘字符
        if (restStart+startNode->charend - startNode->charfrom+1 <= end)
        {
            restStart+=startNode->charend - startNode->charfrom+1;	
        }
        else //start 跳完以後超過了end,搜索完畢
        {
            //在兩個節點之間結束.大k,則匹配到第k-1個.(大3,匹配到第二個.大2,匹配到第一個.大1,則剛好在節點上結束.)		
            offset=restStart+startNode->charend - startNode->charfrom+1 - end;
            break;
        }		
    }
    return startNode;
}


/*把root 的所有孩子打印出來*/
void printTree(knode * root,const char * str,int level=1);

void addToRoot(knode *root,int end,const char *str)//嘗試給root加一個葉子.如果S[end]這個字符已經出現在root的孩子中的第一個字符,則不用添加.
{
	knode * t=root->child;
	while (str[t->charfrom] !=str[end])
	{
		if (t->sibling != NULL)
		{
			t=t->sibling;
		}	
		else
		{
			break;
		}
	}
	if (str[t->charfrom] != str[end])
	{
		t->sibling=new knode();
		t->sibling->parent=t->parent;
		t->sibling->charfrom=end;
		t->sibling->charend=end;
		//curNode=t;
		
		//如果是葉子的話,要設置knode的strIndex.
		if (end==strlen(str+1))
		{
			t->sibling->strIndex=end;
		}
		
	}
}

int sequence=1;
//從字符串str構造後綴樹
knode* createSuffixTree(const char* str)
{
	knode * leaf1=NULL; //總是指向葉子1,保存這個指針是因爲這樣擴展str[1,...,i]時可以直接找到最後一個node.
	//因爲str[j,...,i]中內部節點的suffix link要到因爲str[j+1,...,i]纔可以設置,
//所以這裏保存一個指針.
knode * curInternalNode=NULL;

//在往str[j,...,i]的後綴樹中添加字符str[i+1]時,curNode用來記錄增長的那個節點
	knode * curNode=NULL;.
	int curRule=NORULE; //當前使用的添加字符的規則

    knode*  root=new knode();  
    const int slength=strlen(str)+1;
    strdollar=new char[slength+2];
    memcpy(strdollar+1,str,slength);
    
    //在字符串末尾加個'$',這個符號不該出現在str中作爲字符串的內容.
    strdollar[slength]='$'; 
    strdollar[slength+1]='/0';
    
    cout<<"string is   "<<&strdollar[1]<child)
	{
		root->child=new knode();
		root->child->parent=root;
		leaf1=root->child;
		
		leaf1->charfrom=1;
		leaf1->charend=1;
		curNode=leaf1;
	}


    //字符索引從1開始計數
	int restStart=0;
	for (int end=1; endcharend++; //把節點的字符索引範圍擴大1,就是多表示了一個字符.

				//如果是葉子的話,要設置knode 的strIndex兩個成員.
				if (leaf1->charend == slength)
				{
					leaf1->strIndex=start;						
				}			

                curNode=leaf1;	
                curRule=RULE1;		
                if (start==end && start==1)
                    addToRoot(root,end+1,strdollar);	
                
                #ifdef DEBUG
                printf("/n start printing %d .../n",sequence++);
                printTree(root,strdollar);
                #endif				

                continue;
			}
			
			restStart=start;
			
            
            //這個節點既不是root,也沒有suffix link,那麼需要走動到父節點.suffrom,suffend用來保存從這個節點走動到父節點時跳過的字符串.
			int suffrom=0;
			int suffend=0;
			if (curNode->sufflink == NULL && curNode != root)
			{				
				//走到這一步不可能是RULE3,因爲碰到RULE3的話,這一次擴展已經結束了.開始添加下一個字符了(開始下一次外層循環).
                if (RULE1==curRule) //是在葉子上添加的字符
                {				
                    suffrom=curNode->charfrom;
                    suffend=curNode->charend-1;
                }
                else //RULE2==curRule.
                {
                    suffrom=curNode->charfrom;
                    suffend=curNode->charend;
                }
                curNode=curNode->parent;		
			}
			
			if (curNode != root)//這個節點有suffix link
            {
                assert(curNode->sufflink != NULL);
                curNode=curNode->sufflink;							
                
                if (suffrom !=0) //suffrom 改變過了,同時suffend也改變過了.需要從curNode開始搜索suffrom-suffend這串字符
                {					
                    while (suffrom <= suffend)
                    {
                        assert(curNode->child !=NULL);
                        curNode=curNode->child;                        
                        while (strdollar[curNode->charfrom] != strdollar[suffrom])
                        {
                            assert(curNode->sibling != NULL);
                            curNode=curNode->sibling;
                        }                        
                        suffrom+=curNode->charend-curNode->charfrom+1;
                    }				
                    
                    if(suffrom-suffend ==1)
                    {
                        restStart=suffend+1;				
                    }
                    else
                    {
                        restStart=suffrom;
                    }
                }
                else	//curNode自己就有suffix link,沒有跳到父節點.					
                {
                    restStart=end+1;
                }
		
			}			
			else //curNode是root
			{
				suffrom=0;
				suffend=0;
				restStart=start;			
			}			
		
		//	int curStart=restStart;
			int offset=0;
			if (suffend !=0)
			{
				offset=restStart-suffend;
			}
			else
			{
				offset=restStart-end;
			}
			
			if (start==end)
			{
				is_last_char=true;				
			}
			else
			{
				is_last_char=false;
			}
			
			if (suffend !=0)
			{
				if (restStart <= suffend)
				{
					curNode=searchNode(curNode,restStart,suffend,offset,strdollar);
				}
			}
			else
			{		
				if (restStart <= end)
				{
					curNode=searchNode(curNode,restStart,end,offset,strdollar);
				}
			}
			/*
			offset==1時S[start,...,end]路徑在節點上結束.
			否則的話S[start,...,end]路徑在邊上結束,在邊上結束時拆分節點的必要條件(注意不是充分條件).				
			*/
			 if (offset==1)//S[j..i] ends at a node
			 {
				if(curNode->child==NULL)//rule1: S[j..i] ends at a leaf.
                {					
                    curNode->charend++;			
                    if (is_last_char)//嘗試給root加一個葉子.如果S[end+1]這個字符已經出現在root的孩子中的第一個字符,則不用添加.
                    {
                    addToRoot(root,end+1,strdollar);
                    }                    
                    curRule=RULE1;
                    #ifdef DEBUG
                    printf("/n start printing %d .../n",sequence++);
                    printTree(root,strdollar);
                    #endif

					
				//如果是葉子的話,要設置knode 的strIndex兩個成員.
					if (curNode->charend == slength)
					{
					curNode->strIndex=start;						
					}					
                    
				}
				else// S[j..i] ends at a internal node.find S[i+1]in all the node's childs.
				{				
					curNode=curNode->child;
					while (strdollar[curNode->charfrom] != strdollar[end+1])
					{
						if (curNode->sibling !=NULL)
						{
							curNode=curNode->sibling;
						}
						else
						{
							break;
						}

					}
					//search is not succesful.  
					if (curInternalNode!=NULL)
					{
						curInternalNode->sufflink=curNode->parent;
						curInternalNode=NULL;
					}					
					//rule 2. add a new leaf.
					if (strdollar[curNode->charfrom] != strdollar[end+1])
                    {
                        curRule=RULE2;					
                        curNode->sibling=new knode();
                        curNode->sibling->parent=curNode->parent;
                        curNode->sibling->charfrom=end+1;
                        curNode->sibling->charend=end+1;

					
					//如果是葉子的話,要設置knode 的strIndex兩個成員.
					if (curNode->sibling->charend == slength)
					{
					curNode->sibling->strIndex=start;						
					}

                        if (is_last_char)
                        addToRoot(root,end+1,strdollar);
                        
                        curNode=curNode->parent;
                        
                        #ifdef DEBUG
                        printf("/n start printing %d .../n",sequence++);
                        printTree(root,strdollar);
                        #endif
					}
					else //rule 3:
					{
						curRule=RULE3;
						if (is_last_char)
						{
							addToRoot(root,end+1,strdollar);
							#ifdef DEBUG
							printf("/n start printing %d .../n",sequence++);
							printTree(root,strdollar);
							#endif
						}

					//如果是葉子的話,要設置knode 的strIndex兩個成員.
					if (curNode->charend==slength)
					{
						curNode->strIndex=start;						
					}						
						//continue;
						break;//Speedup trick 2
					}

				}
			}			
			else //end in a edge. rule2: 要拆分節點
			{							
				//要判斷S[end+1]是不是已經在路徑中了.				
				if(strdollar[curNode->charend-(offset-1)+1]==strdollar[end+1])
				{
					if (is_last_char)
                        addToRoot(root,end+1,strdollar);					
					break;//Speedup trick 2
				}
				curRule=RULE2;
				//rule2: split node and create new leaf.注意:新增內部節點的suffix link要麼已經存在,要麼將在下一次擴展(內部循環)時產生。
				//把t做成新增加的內部節點.在父子關係中和兄弟關係中用t替換curNode.
				knode *t=new knode(); //
				t->parent=curNode->parent;
				t->sibling=curNode->sibling;
				t->child=curNode;
				
				knode *findsibling=curNode->parent->child; //curNode可能不是第一個孩子,所以需要遍歷
				if (findsibling==curNode)
				{
					curNode->parent->child=t;
				}
				else
				{
					while (findsibling->sibling != curNode)
					{
					assert(findsibling->sibling != NULL);
					findsibling=findsibling->sibling;
					}
				}
                findsibling->sibling=t;
                t->charfrom=curNode->charfrom;
                t->charend=curNode->charend-(offset-1);	

                curNode->charfrom=t->charend+1;
                curNode->parent=t;
                
                //注意,拆分以後curNode只有一個兄弟了,就是新增加的這個葉子.他以前的兄弟變成了新父親的兄弟
                curNode->sibling=new knode();
                curNode->sibling->parent=t;				
                curNode->sibling->charfrom=end+1;
                curNode->sibling->charend=end+1;

				//如果是葉子的話,要設置knode 的strIndex兩個成員.
				if (curNode->sibling->charend==slength)
					{
						curNode->sibling->strIndex=start;							
					}
				
				//如果有沒有設置過suffix link的節點的話,對他進行設置。這樣的節點只會有一個。
				//注意:新增內部節點的suffix link要麼已經存在,要麼將在下一次擴展(內部循環)時產生。
				if(curInternalNode != NULL)
				{
					curInternalNode->sufflink=t;
					curInternalNode=NULL;
				}
								
				//只表示一個字符的內部節點,現在就可以設置suffix link 指向root節點.
				//07-11-19修正.
				if (t->parent == root && t->charfrom == t->charend)
				{
					t->sufflink=root;					
				}
				else
				{
					//t 的suffix link將在下一次設置
					curInternalNode=t;
				}
				//curNode總是存放增長的那個節點,那個節點的路徑必須是樹中已經有的,不算增加的字符,
				//故這裏不指向葉子,而指向新的內部節點t.
				curNode=t;				
				if (is_last_char)
					addToRoot(root,end+1,strdollar);
			
				#ifdef DEBUG
				printf("/n start printing %d .../n",sequence++);
				printTree(root,strdollar);
				#endif

			}			

		}//end of for (int start=0; startchild)
	{
		return;
	}
	knode *t=root->child;
	while (t != NULL)
	{
		if (t->parent == groot)
		{
			printf("/n(+)");
		}
		for (int i=t->charfrom; i<=t->charend; i++)
		{
			
			printf("%c",str[i]);
		}
		if (t->child)
		{
		
			printf("/n");
			for (int j=1; j<=level; j++)
			{
				printf("  |");
			}
			printf("+");
			printTree(t,str,level+1);
		}
		else //t是葉子
		{
			printf("(leaf %d-%d)",t->strIndex,t->charend);
		
		}
		
		if(t->sibling)
		{
			printf("/n");
			for (int j=1; jsibling;
	}
}


int main(int argc, char* argv[])
{
    char teststr[]="abacda";
    knode * result=createSuffixTree(teststr); groot=result;    
    printTree(result,strdollar,1);
    printf("/n");    
    return 0;
}
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章