键树的c++代码实现
2007-11-07 18:30
375 查看
建树时使用的是Ukkonens算法.因为看的都是英文资料,所以很吃力。加上这个算法很复杂,偶看了好几天。主要是在后缀链上老是出问题。
对于后缀链,我觉得有几点比较重要,可以帮助你理解建树的过程。总结如下:
(1)suffixlink只能在内部节点之间出现.也就说只能从内部节点指向内部节点.
所以如果节点是叶子的话,就不用考虑了。
(2)一个新的内部节点总是由规则2产生的。规则2要拆分节点。
(3)一个新的内部节点产生的时候,要么他的suffixlink已经存在,要么suffixlink将会在下一次扩展时出现(可能是新创建的,也可能是添加叶子时的那个内部节点,只有这两种情况)。这里的下一次扩展是指如果当前这一次是搜索S[j-1,...,i],并添加字符S[i+1],那么下一次扩展是搜索S[j,...,i],并添加字符S[i+1].
第(3)条比较长,不过很重要。
其他的细节可以拿着资料一遍一遍的看。
有兴趣的朋友,也可以联系我,我可以推荐几篇我自己认为对我帮助比较大的文章给你。
这个程序我用随机产生的字符串测试,还没有发现问题。
改天有时间写一个应用上来演示一下后缀树的应用。
用后缀树来求解问题也是件麻烦的事情。
水平有限,难免有错误之处。欢迎大家批评指正。
对于后缀链,我觉得有几点比较重要,可以帮助你理解建树的过程。总结如下:
(1)suffixlink只能在内部节点之间出现.也就说只能从内部节点指向内部节点.
所以如果节点是叶子的话,就不用考虑了。
(2)一个新的内部节点总是由规则2产生的。规则2要拆分节点。
(3)一个新的内部节点产生的时候,要么他的suffixlink已经存在,要么suffixlink将会在下一次扩展时出现(可能是新创建的,也可能是添加叶子时的那个内部节点,只有这两种情况)。这里的下一次扩展是指如果当前这一次是搜索S[j-1,...,i],并添加字符S[i+1],那么下一次扩展是搜索S[j,...,i],并添加字符S[i+1].
第(3)条比较长,不过很重要。
其他的细节可以拿着资料一遍一遍的看。
有兴趣的朋友,也可以联系我,我可以推荐几篇我自己认为对我帮助比较大的文章给你。
这个程序我用随机产生的字符串测试,还没有发现问题。
改天有时间写一个应用上来演示一下后缀树的应用。
用后缀树来求解问题也是件麻烦的事情。
水平有限,难免有错误之处。欢迎大家批评指正。
#include"stdafx.h" #include #include #include #include #include usingnamespacestd; #defineRULE14 #defineRULE25 #defineRULE36 #defineNORULE7 //#defineDEBUG /* *keywordtreenode *weusethestructureleftchild-rightsibling *tostorethetree'snodes */ classknode { public: knode() { child=NULL; sibling=NULL; parent=NULL; sufflink=NULL; // strNo=0; strIndex=0; charfrom=0; charend=0; //leftC=0; //leftDiverse=false; } //在叶子节点中保存第strNo个字符串,第strIndex位置 // intstrNo; intstrIndex; //存储的字符串用索引表示从charfrom开始,到charend结束. intcharfrom; intcharend; knode*child;/*leftpointerpointingtochild*/ knode*sibling;/*leftpointerpointingtosibling*/ knode*parent;/*parentnode*/ knode*sufflink;/*suffixlinkinsuffixtree*/ }; knode*groot;//打印树的时候用到 char*strdollar=NULL; /* 搜索结束之有三种情况: (1)在内部节点上结束.给这个内部节点加一个叶子. (2)在叶子上结束.直接将字符加到叶子上. (3)在两个节点之间结束.此时需要拆分节点.此时offset为节点上搜索时匹配字符的偏移,前面两种情况偏移都为1. */ knode*searchNode(knode*curNode,intrestStart,intend,int&offset,constchar*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; returnstartNode; } } //Speeduptrick1:跳过此节点的剩余字符 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; } } returnstartNode; } /*把root的所有孩子打印出来*/ voidprintTree(knode*root,constchar*str,intlevel=1); voidaddToRoot(knode*root,intend,constchar*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=newknode(); 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; } } } intsequence=1; //从字符串str构造后缀树 knode*createSuffixTree(constchar*str) { knode*leaf1=NULL;//总是指向叶子1,保存这个指针是因为这样扩展str[1,...,i]时可以直接找到最后一个node. //因为str[j,...,i]中内部节点的suffixlink要到因为str[j+1,...,i]才可以设置, //所以这里保存一个指针. knode*curInternalNode=NULL; //在往str[j,...,i]的后缀树中添加字符str[i+1]时,curNode用来记录增长的那个节点 knode*curNode=NULL;. intcurRule=NORULE;//当前使用的添加字符的规则 knode*root=newknode(); constintslength=strlen(str)+1; strdollar=newchar[slength+2]; memcpy(strdollar+1,str,slength); //在字符串末尾加个'$',这个符号不该出现在str中作为字符串的内容. strdollar[slength]='$'; strdollar[slength+1]='/0'; cout<<"stringis"<<&strdollar[1]<child) { root->child=newknode(); root->child->parent=root; leaf1=root->child; leaf1->charfrom=1; leaf1->charend=1; curNode=leaf1; } //字符索引从1开始计数 intrestStart=0; for(intend=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); #ifdefDEBUG printf("/nstartprinting%d.../n",sequence++); printTree(root,strdollar); #endif continue; } restStart=start; //这个节点既不是root,也没有suffixlink,那么需要走动到父节点.suffrom,suffend用来保存从这个节点走动到父节点时跳过的字符串. intsuffrom=0; intsuffend=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)//这个节点有suffixlink { 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自己就有suffixlink,没有跳到父节点. { restStart=end+1; } } else//curNode是root { suffrom=0; suffend=0; restStart=start; } // intcurStart=restStart; intoffset=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]endsatanode { if(curNode->child==NULL)//rule1:S[j..i]endsataleaf. { curNode->charend++; if(is_last_char)//尝试给root加一个叶子.如果S[end+1]这个字符已经出现在root的孩子中的第一个字符,则不用添加. { addToRoot(root,end+1,strdollar); } curRule=RULE1; #ifdefDEBUG printf("/nstartprinting%d.../n",sequence++); printTree(root,strdollar); #endif //如果是叶子的话,要设置knode的strIndex两个成员. if(curNode->charend==slength) { curNode->strIndex=start; } } else//S[j..i]endsatainternalnode.findS[i+1]inallthenode'schilds. { curNode=curNode->child; while(strdollar[curNode->charfrom]!=strdollar[end+1]) { if(curNode->sibling!=NULL) { curNode=curNode->sibling; } else { break; } } //searchisnotsuccesful. if(curInternalNode!=NULL) { curInternalNode->sufflink=curNode->parent; curInternalNode=NULL; } //rule2.addanewleaf. if(strdollar[curNode->charfrom]!=strdollar[end+1]) { curRule=RULE2; curNode->sibling=newknode(); 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; #ifdefDEBUG printf("/nstartprinting%d.../n",sequence++); printTree(root,strdollar); #endif } else//rule3: { curRule=RULE3; if(is_last_char) { addToRoot(root,end+1,strdollar); #ifdefDEBUG printf("/nstartprinting%d.../n",sequence++); printTree(root,strdollar); #endif } //如果是叶子的话,要设置knode的strIndex两个成员. if(curNode->charend==slength) { curNode->strIndex=start; } //continue; break;//Speeduptrick2 } } } else//endinaedge.rule2:要拆分节点 { //要判断S[end+1]是不是已经在路径中了. if(strdollar[curNode->charend-(offset-1)+1]==strdollar[end+1]) { if(is_last_char) addToRoot(root,end+1,strdollar); break;//Speeduptrick2 } curRule=RULE2; //rule2:splitnodeandcreatenewleaf.注意:新增内部节点的suffixlink要么已经存在,要么将在下一次扩展(内部循环)时产生。 //把t做成新增加的内部节点.在父子关系中和兄弟关系中用t替换curNode. knode*t=newknode();// 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=newknode(); 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; } //如果有没有设置过suffixlink的节点的话,对他进行设置。这样的节点只会有一个。 //注意:新增内部节点的suffixlink要么已经存在,要么将在下一次扩展(内部循环)时产生。 if(curInternalNode!=NULL) { curInternalNode->sufflink=t; curInternalNode=NULL; } //只表示一个字符的内部节点,现在就可以设置suffixlink指向root节点. //07-11-19修正. if(t->parent==root&&t->charfrom==t->charend) { t->sufflink=root; } else { //t的suffixlink将在下一次设置 curInternalNode=t; } //curNode总是存放增长的那个节点,那个节点的路径必须是树中已经有的,不算增加的字符, //故这里不指向叶子,而指向新的内部节点t. curNode=t; if(is_last_char) addToRoot(root,end+1,strdollar); #ifdefDEBUG printf("/nstartprinting%d.../n",sequence++); printTree(root,strdollar); #endif } }//endoffor(intstart=0;startchild) { return; } knode*t=root->child; while(t!=NULL) { if(t->parent==groot) { printf("/n(+)"); } for(inti=t->charfrom;i<=t->charend;i++) { printf("%c",str[i]); } if(t->child) { printf("/n"); for(intj=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(intj=1;jsibling; } } intmain(intargc,char*argv[]) {
charteststr[]="abacda"; knode*result=createSuffixTree(teststr);groot=result; printTree(result,strdollar,1); printf("/n"); return0; }
相关文章推荐
- 【减治法】插入排序及C++代码实现
- 工厂模式及C++代码实现
- 原型模式及C++代码实现
- 用Swig将c/c++程序转为java代码(使用swig实现java调用c、c++的方法)
- bit-map简介及其C/C++代码实现
- 插入排序(正序、倒序)-c++代码实现及运行实例结果
- 开启全屏视频播放,全屏开启ie浏览器的C++代码实现
- 循环单链表的实现代码C++
- 查找-c++代码实现及运行实例结果
- 查找-c++代码实现及运行实例结果
- 归并排序-c++代码实现及运行实例结果
- 归并排序-c++代码实现及运行实例结果
- Bezier曲线原理及实现代码(c++)
- 选择排序-c++代码实现及运行实例结果
- 选择排序-c++代码实现及运行实例结果
- 快速傅里叶变换(FFT)算法C++实现代码
- 大规模数据处理Bloom Filter C++代码实现
- 如何让代码实现C++
- GBK转UTF8 C++实现代码
- C++实现split,若有问题,请指正。(有用C实现过的,欢迎贴代码)