详解线索二叉树
遍历二叉树是对非线性结构进行线性化操作,在得到的访问序列中,每个结点都只有一个直接前驱和一个直接后继。(除区头尾两个结点)
引入线索二叉树可以加快查找前驱与后继结点的速度,实质就是将二叉链表中的空指针改为指向前驱或后继的线索,线索化就是在遍历中修改空指针。
通常规定:对某一结点,若无左子树,将lchild指向前驱结点;若无右子树,将rchild指向后继结点。
还需要设置左右两个tag,用来标记当前结点是否有子树。
若ltag==1,lchild指向结点前驱;若rtag==1,rchild指向结点后继。
线索二叉树的存储结构如下:
typedef struct ThreadNode{
ElemType data;
struct ThreadNode *lchild, *rchild;
int ltag, rtag;
}ThreadNode, *ThreadTree;
首先建立一棵二叉树,默认输入是按照先序遍历:
void CreateThreadTree(ThreadTree &T)
{
char c;
cin >> c;
if('#' == c)
T = NULL;
else{
T = new ThreadNode;
T->data = c;
CreateThreadTree(T->lchild);
CreateThreadTree(T->rchild);
}
}
现在我们通过中序遍历建立中序线索二叉树,即对给定的二叉树,递归进行线索化操作:
void CreateInThread(ThreadTree T)
{
ThreadTree pre = NULL;
if(T != NULL)
{
InThread(T, pre); //对非空二叉树线索化
pre->rchild = NULL; //处理遍历的最后一个结点
pre->rtag = 1;
}
}
其中通过中序遍历对二叉树线索化的递归算法为:
void InThread(ThreadTree &p, ThreadTree &pre) //pre始终指向中序遍历时上一个刚刚访问过的结点;
{
if(p != NULL)
{
InThread(p->lchild, pre); //递归线索化左子树;
if(p->lchild == NULL) //左子树为空,建立前驱线索;
{
p->lchild = pre;
p->ltag = 1;
}
if(pre != NULL && pre->rchild == NULL) //建立前驱结点的后继线索;
{
pre->rchild = p;
pre->rtag = 1;
}
pre = p; //标记当前结点成为刚刚访问过的结点;
InThread(p->rchild, pre); //递归线索化右子树;
}
}
线索二叉树已经构造完成。现在利用线索二叉树,可以实现二叉树遍历的非递归算法。以中序遍历为例:
要实现中序遍历,可以先求出中序线索二叉树的中序序列中第一个结点,然后调用求某个结点的后继结点算法,直到遍历完成。
求中序线索二叉树的中序序列中第一个结点算法:
ThreadNode *FirstNode(ThreadNode *p)
{
while(p->ltag != 1) //如果有左孩子,迭代至左孩子
p = p->lchild;
return p;
}
求中序线索二叉树中序序列中某个结点的后继结点算法:
ThreadNode *NextNode(ThreadNode *p)
{
if(p->rtag != 1) //如果有右孩子,则该结点的后继结点为该结点右子树在中序序列中的第一个结点;
return FirstNode(p->rchild);
else
return p->rchild; //如果没有右孩子,直接返回后继结点;
}
最后,实现中序遍历:
void InOrderThread(ThreadNode *T)
{
for(ThreadNode *p = FirstNode(T); p!= NULL; p = NextNode(p))
{
cout << p->data << " ";
}
}
主函数如下:
int main()
{
ThreadTree T; //定义一颗二叉树;
CreateThreadTree(T); //构造二叉树;
CreateInThread(T); //对二叉树线索化;
InOrderThread(T); //对线索二叉树实现非递归的中序遍历;
return 0;
}
运行结果如图所示: