不知道你是否和我当时一样,对于线索二叉树,有点云里雾里的感觉,现在我们来一起探讨下吧。
首先,我们所应该知道的是:线索二叉树是对二叉链表中空指针的充分利用,也就是说,使得原本是空指针的转化成在某种遍历的顺序下,指向该结点的前驱和后继。也许听的有点糊涂,没关系,请接着往下看。
在二叉链表中,每个结点都带有*leftChild和*rightChild,两个指针,而除根结点外,每个结点只被一个指针所对应,要么是leftChild,要么是rightChild.而总共有2*n个指针,也就是说,有2*n-(n-1)个空指针,从这个角度,也说明了线索二叉树的必要性。
线索二叉树在二叉链表的基础上增加了两个成员数据:leftTag,rightTag;用来标记当前结点的leftChild,rightChild指针指向的是孩子,还是线索。leftTag=rightTag=1,表示线索,leftTag=rightTag=0;表示孩子;
下面给出中序遍历顺序下的线索二叉树的构造函数,*p为根;
template <class ElemType>
void InThreadBinTree<ElemType>::InThreadHelp(ThreadBinTreeNode<ElemType> *p,ThreadBinTreeNode<ElemType> *pre)
{
if(p!=NULL)
{
InThreadHelp(p->leftChild,pre);
if(p->leftChild==NULL)
{
p->leftChild=pre;
p->leftTag=1;
}
else
p->leftTag=0;
if(pre!=NULL&&pre->rightChild==NULL)
{
pre->rightChild=p;
pre->rightTag=1;
}
else if(pre!=NULL)
{
pre->rightTag=0;
}
pre=p;
InThreadHelp(p->rightChild,pre);
}
}
该段程序可被分为3部分,1.左孩子的线索化,2.p的线索化,3.p右孩子的线索化。
在中序遍历的前提下,判断p是否有空指针,然而,再遍历右子树之前,如果p->rightChild=NULL是无法确定p的后继的,因此,此时,只能判断p->leftChild是否为空,若为空,则leftChild=pre;此时,判断pre->rightChild是否为空,如为空,pre->rightChild=p;
p=pre;你也许注意到了,此时pre->rightChild,也就是先前p->rightChild,此时可以判断了,并且后继就是现在的p;此时所有空指针的线索化已经完成。当然,第一个结点的leftchild,和最后一个结点的rightchild是空指针,因为他们不需要线索化。
那么问题又来了,做了这么多,线索二叉树有什么用,或者说要怎么用。
以下是个人理解;
通过构造线索二叉树,我们可以很容易确定树中任何一个结点在某种遍历顺序下的前驱或者后继。不妨以中序遍历为例;
树中必然存在这样两种结点:被线索化了的,以及没有被线索化的
被线索化的,leftTag==1,leftchild即为前驱,rightTag==1,rightChild即为后继;
若leftTag==0,则该结点的前驱通过leftchild为根的树的rightchild循环,直到rightTag==1,即为其前驱,
若rightTag==0,则通过rightchild为根的leftchild循环,直到leftTag==1,即为其后继。
也就是说,任何结点按中序遍历的顺序的前驱和后继都能找到。这就是线索二叉树的作用,方便确定某种遍历的顺序。