目录
下一篇: 树的顺序存储结构
一、引出
如何在普通二叉树中找到一个中序遍历中任意结点的前驱?
例如:中序遍历:DGBEAFC
要找F的前驱A要怎么做?
一种办法:首先从根结点开始遍历找到F结点,然后将p指针指向F,然后再次从根结点遍历,指针q指向当前访问结点,pre指向上一个被访问的结点,当q==p时pre指向的A就是F的前驱
代码实现:
//中序遍历
void InOrder(BiTree T){
if(T!=NULL){
InOrder(T->lchild);//递归遍历左子树
visit(T);
InOrder(T->rchild);//递归遍历右子树
}
}
辅助变量
BiTNode *p,*pre=NULL,*fina=NULL;//p是目标结点,pre前驱,final是最终结果
//访问结点q
void visit(BiTNode *q){
if(q==p){
final=pre;
}
else=pre=q;
}
二、中序线索二叉树
有n个结点的二叉树,有n+1个空链域!可用来记录前驱、后继的信息
二叉树线索化
前驱线索(由左孩子指针充当)
后继线索(由右孩子指针充当)
先序线索二叉树:按先序遍历进行线索化
后续线索二叉树同理
小结:
三、代码实现二叉树线索化
中序线索化:
//中序线索化二叉树T
//初始化时所有的线索标志要都为0
void CreateInThread(ThreadTree T){
pre=NULL;
if(T!=NULL){
InThread(T);
if(pre->rchild==NULL){//处理最后一个结点
pre->rtag=1;
}
}
}
void visit(ThreadNode *q){
if(q->lchild==NULL){//左子树为空,建立前驱线索
q->lchild=pre;
q->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL){//处理上一个结点的后继线索
pre->rchild=q;
pre->rtag=1;
}
pre=q;//指向下一个结点
}
//中序遍历二叉树,一边遍历一遍线索化
void InThread(ThreadTree T){
if(T!=NULL){
InThread(T->lchild);//左
visit(T);//根
InThread(T->rchild);//右
}
}
先序线索二叉树
/*
先序遍历有点不一样,因为是先visit后访问左孩子
有可能在该结点的左指针线索化后指向上一个指针,
又这时要访问左结点就刚好访问到了上一个结点去,而产生死循环
所以在访问左孩子结点前得先判断tag是否等于0
visit不变
*/
void PreThread(ThreadTree T){
if(T!=NULL){
visit(T);
if(T->ltag==0){
PreThread(T->lchild);
}
PreThread(T->rchild);
}
}
void CreatPreThread(ThreadTree T){
pre=NULL;
if(T!=NULL){
PreThread(T);
//处理最后一个结点
if(pre->rchild==NULL)
pre->rtag=1;
}
}
后序略:(几乎同中序一样)
由于后序线索化就是,后序遍历一遍,且是先处理左右孩子,所以不会产生死循环
小结:
四、在线索二叉树中找前驱、后继
找后继
在中序线索二叉树中找指定结点*p的中序后继next
分两种:
1、若p->rtag=1,则next=p->rchild
2、若p->ratg=0,则p必有右孩子
则情况2,需要中序遍历p的右子树,直到找到右子树最左下结点(不一定是叶结点哦!)
代码:
//找到以p为根的子树中,第一个被中序遍历的结点
ThreadNode *FirstNode(ThreadNode *p){
//循环找到最左下结点
while(p->ltag=0)p=p->lchild;
return p;
}
//在中序线索二叉树中找到结点p的后继结点
ThreadNode *NextNode(ThreadNode *p){
//右子树中最左下结点,如果该结点有右孩子
if(p->rtag==0)return FirstNode(p->rchild);
else return p->rchild; //rtag=1则直接返回后继线索
}
//利用这个特性对线索二叉树进行中序遍历
void _InOrder(ThreadNode *T){
//先找到子树的第一个最左结点,再不断的循环找中序后继结点并访问
for(ThreadNode *p=FirstNode(T);p!=NULL;p=NextNode(p)){
visit(p);
}
}
找前驱
分两种情况
1、p->ltag=1,则pre=p->lchild
2、p->ltag=0,则找p的左子树中最右下的结点
代码:
//找到以p为根的子树中,最后一个被中序遍历的结点
ThreadNode *LastNode(ThreadNode *p){
//循环找到最右下结点
while(p->rtag==0)p=p->rchild;
return p;
}
//在中序线索二叉树中找到结点p的前驱结点
ThreadNode *PreNode(ThreadNode *p){
//找该结点的左子树的最右下角结点
if(p->rtag==0)return LastNode(p->lchild);
else return p->lchild;
}
//利用这一特性,对中序线索二叉树进行逆向遍历
void RevInOrder(ThreadNode *T){
//先找到子树的的最右结点,再依次往前遍历
for(ThreadNode *p=LastNode(T);p!=NULL;p=PreNode(p)){
visit(p);
}
}
先序线索二叉树
找后继:
1、若p->rtag==1,则next=p->rchild
2、若tag==0,则:
有左则左,有右则右
找前驱:
1、tag=1则pre=p->lchild
2、tag=0则做不到
除非用从头遍历的土办法
或者该树是个三叉链表二叉树(有个指向父结点的指针)
第三种:简而言之就是,找p的兄弟子树的最后一个被遍历的(该子树右有右孩子就接着找,若还有左孩子也接着找)
如图:
第四种:就是p就是根结点,则p没有先序前驱
后序线索二叉树
找后序前驱
1、ltag==1,pre=p->lchild
2、ltag==0
找后继
1、rtag==1,next=p->rchild
2、rtag==0,则找不到,因为左右子树只可能是前驱
只能用土办法从头遍历
或则是三叉链表二叉树
第三种方法同上先序线索二叉树,但是是找右兄弟结点子树中第一个被访问的结点(左找完找右)
小结: