在前面的博客中, 我们已经学习了给二叉树加中序线索的方法. 接下来我们来学习中序遍历线索二叉树的方法.
首先, 我们需要找到二叉树T中第一个被中序遍历的结点: 根据中序遍历的算法逻辑, (※)也就是找到二叉树T的最左下结点. 下面的算法可定位到二叉树的最左下结点.
BiTreeNode* FirstNode(BiTreeNode* T)//在以T为根的子树中, 寻找最左下结点(中序遍历中第一个被访问的结点)
{
if(T->LTag==1)//根节点的左子树为空
{
//那么第一个被中序遍历的结点就是根节点T
return T;
}
else//T的左子树不空
{
while(T->LTag==0)//如果该结点的LTag==1, 则该结点的左子树为空, 也即找到以T为根的子树最左下的结点
{
T=T->LChild;
}
return T;
}
}
接着, 我们需要找到某结点在中序遍历序列下的后继结点: 如果该结点的RChild指针已被线索化, 那么该结点的右孩子就是要寻找的中序后继结点; 如果该结点的RChild结点未被线索化, 那么以该结点的右孩子为树根,寻找以该结点右孩子为树根的二叉树中第一个被中序遍历的结点.
BiTreeNode* NextNode(BiTreeNode* T)//在中序线索二叉树中找到T的后继结点
{
if(T->RTag==1)//该结点的右孩子指针域已被线索化, 直接返回该结点的右孩子指针即可
{
return T->RChild;
}
else//该结点的右孩子指针域未被线索化
{
return FirstNode(T->RChild);//将该结点的右孩子指针传给FirstNode()函数, 并通过该函数找到该结点在中序序列下的后继
}
}
有了上面两算法的支撑, 我们直接写出中序遍历线索二叉树的算法.
void InOrderTraverse(BiTreeNode* T)
{
BiTreeNode* p=FirstNode(T);//寻找树T中第一个被中序遍历的结点
while(p!=NULL)
{
cout<<p->data<<" ";//访问结点
p=NextNode(p);//p指向该结点的中序后继结点
//当访问中序序列的最后一个结点后, p=NULL, 回到while()的判断语句时, 循环结束
}
cout<<endl;
}
中序线索二叉树是三种线索二叉树中最重要的一个,读者应仔细体会其算法逻辑,并将其掌握.
上面我已经介绍了中序遍历线索二叉树的方法,下面我们再来讨论该如何逆中序遍历线索二叉树.
要想逆中序遍历线索二叉树,首先要找到二叉树T中最后一个被中序遍历的结点,(※)也就是找到二叉树T的最右下结点.
BiTreeNode* LastNode(BiTreeNode* T)//在以T为根的二叉树中, 找到最后一个被中序遍历的结点(最右下结点)
{
while(T->RTag==0)//T指向结点的右孩子不空
{
T=T->RChild;//T指针循环向右下移动
}
return T;
}
接下来,我们还需要寻找某结点的中序前驱结点: 如果该结点的LChild指针已被线索化,那么该结点的LChild指向其中序前驱结点;如果该结点的LChild指针未被线索化,那么以该结点的左孩子为树根,寻找以该结点左孩子为树根的二叉树中最后一个被中序遍历的结点.
BiTreeNode* PreNode(BiTreeNode* T)//寻找T指向结点的前驱结点
{
if(T->LTag==1)//T指向结点的LChild指针已被线索化
{
return T->LChild;
}
else//T指向结点的LChild指针未被线索化, 即左子树不空
{
//寻找T指向结点的左子树中最后一个被中序遍历的结点
return LastNode(T->LChild);
}
}
有了上面的算法,直接给出逆中序遍历线索二叉树的算法.
void RevInOrderTraverse(BiTreeNode* T)//逆中序遍历二叉树
{
BiTreeNode* p=LastNode(T);//找到树T中最后一个被中序遍历的结点
while(p!=NULL)
{
cout<<p->data<<" ";
p=PreNode(p);//p指向当前访问结点的前驱
//当访问过最后一个结点后, p=NULL, 回到while()的判断语句时, 循环结束
}
}
除了要牢牢掌握上面的两大算法外,我感觉很有必要再次对比普通二叉树和线索二叉树的操作方式,以更好地理解这两种二叉树的操作. 下面我们来总结一下线索二叉树的重要操作逻辑.
和普通二叉树不同的是,当给线索二叉树加入线索后,不能再以普通二叉树的标准来判断某结点是否有左孩子或右孩子,而需要以左孩子线索标志LTag和右孩子线索标志RTag的值(LTag=0->左孩子存在, LTag=1->左孩子不存在, LChild指针指向该结点的遍历序列前驱; RTag=0->右孩子存在, RTag=1->右孩子不存在, RChild指针指向该结点的遍历序列后继)来判断.
在(中序)遍历时,也不能像普通二叉树那样递归调用就可以实现,其遍历算法复杂度>普通二叉树遍历算法复杂度:首先需要找到二叉树T中第一个被中序遍历的结点;之后访问每个结点时,都要先判断该结点的RChild指针是否已被线索化,(如果已被线索化, 那么该结点的RChild指针指向(中序)后继结点)如果没有被线索化(说明该结点的右孩子一定存在),那么还需要找到以该结点右孩子为树根的二叉树中第一个被中序遍历的结点. 简单一点说,遍历线索二叉树时,如果结点的RChild指针已被线索化,那么RChild指针指向的结点就是遍历序列后继结点;如果结点的RChild指针未被线索化,那么需要寻找以该结点右孩子为树根的二叉树中第一个(按照遍历序列)被访问的结点.
至此,我们可以发现,线索二叉树实际上是在用算法设计代价换取算法执行效率.