零、说明
- 线索化有点复杂,仔细看,画图
- 线索化意义在于充分利用空置指针,以提高遍历的效率
- 完成后,遍历二叉树相当于遍历一个双向循环链表(以下说的是是中序遍历)
①头节点左指针 指向 树的根节点,右指针 指向 遍历的最后一个节点。
②树中,遍历的第一个节点的左指针 指向 头节点,
③原来的左空指针指向上一个遍历到的节点,右空指针指向下一个遍历到的节点
④最后一个遍历到的节点的右指针指向头节点
头节点->根节点->…中序遍历到的第一个节点->…遍历的最后一个节点
↑----------------------------------------↓(左指针)<-------------↓(右指针)
- 先序和后序待完成
一、数据类型
typedef char Eltp;
typedef struct BTreenode
{
Eltp data;
int ltag,rtag;//0为指左右孩子,1为指下一线索
struct BTreenode *lch,*rch;
}BTreenode,*BTree;
二、准备工作
- Create函数,带#先序输入二叉树内容(见树3——二叉树(链式)),注意还要让p->ltag=0,p->rtag=0,每个节点在开始时左右tag都应为0
BTree pre;//全局变量,始终指向上一个访问的节点
- 主函数中要声明两个BTree类型H,T,一个是树的根节点T,一个作为遍历的头节点H,到时候遍历的时候不用根节点T,就用这个头节点H
三、线索化
(前面准备阶段声明了一个全局变量BTree pre,现在就要用上)
主角是头节点H,处理得最多之后遍历用H
void Inthread(BTree T,BTree *H)
{
(*H)=new BTreenode;//下五行是这个“头节点”的设定
(*H)->ltag=0;//lch是正常的左孩子,指向根节点
(*H)->lch=T;
(*H)->rtag=1;//rch其实要指向最后一个节点,但是现在还没,先指向自己,待下面的函数使用
(*H)->rch=(*H);
pre=(*H);//第一个遍历根节点,则前一个结点是(*H)
Inthreading(T);
pre->rtag=1;//Inthreading调用结束后,pre就是遍历的最后一个节点了
pre->rch=(*H);//其实,中序遍历,最后一个节点必然没有右孩子,如果有,它就不是最后一个节点,右孩子才是了。所以直接让右孩子等于头节点
(*H)->rch=pre;//理由见本文零、3.①
}
void Inthreading(BTree p)//可能并非整棵树,而是部分节点,所以用p
{
if(p)//p为空就直接返回
{
Inthreading(p->lch);//中序,所以先来个p->lch,直到没有左孩子,就过去了
if(p->lch==NULL)//如果左指针为空,想办法利用上
{
p->lch=pre;
p->ltag=1;
}
if(pre->rch==NULL)//重要:现在未知下一个遍历的节点到底在哪,所以处理pre即可,p的右孩子等下一轮现在的p成为pre之后再搞
{
pre->rch=p;
pre->rtag=1;
}
pre=p;//p处理完了,p就是前一个遍历到的节点了
Inthreading(p->rch);
}
}
四、遍历
注意传入的是上面线索化完得到的头节点H,而不是树的根节点T
再注意:这个遍历和普通二叉树不一样,是非递归写法
void Inorder(BTree H)
{
BTree p;
p=H->lch;//从树的根节点开始
while(p!=H)//如果T为空树,结束,如果遍历完了(此时p指到T),也结束
{
while(p->ltag==0)//先到能下去的最左的左孩子,这个节点就是中序遍历到的第一个
{
p=p->lch;
}
cout<<p->data;//输出
while(p->rtag==1&&p->rch!=H)//接下来一直跟随线索走,直到这个节点有右孩子,
//那它下一个就不是线索了,或者下一个是H,那就是遍历完了,马上出循环
{
p=p->rch;
cout<<p->data;
}
p=p->rch;//如果p的rtag不为1,即它有右子树(这还不一定只有一个右孩子节点,可能是右子树了)
//那么下一个被遍历到的其实是它的右子树最靠左的节点,思路同循环第一行的while(p->ltag==0)
}
}