您的位置:首页 > 编程语言 > C语言/C++

键树的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)条比较长,不过很重要。
其他的细节可以拿着资料一遍一遍的看。
有兴趣的朋友,也可以联系我,我可以推荐几篇我自己认为对我帮助比较大的文章给你。

这个程序我用随机产生的字符串测试,还没有发现问题。
改天有时间写一个应用上来演示一下后缀树的应用。
用后缀树来求解问题也是件麻烦的事情。

水平有限,难免有错误之处。欢迎大家批评指正。

#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;
}

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: