建樹時使用的是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;
}