数据结构入门--线索二叉树

当我们遍历一棵二叉树时,我们会得到整棵树数据的一个序列(前or中or后),可以知道一个数据结点的前驱或者后继是哪个结点。但是,对于每个结点的结构,我们只能从中得知某结点的左右孩子的信息,要得到前驱/后继结点的信息就要遍历一遍二叉树,这样岂不是很麻烦?我们能不能有一种方法,可以轻易的知道某个结点的前驱结点或后继结点呢?答案显然是肯定的

我们知道二叉树的一个结点有两个指针域,那么对于n个结点的二叉树就会有2n个指针域。而且n个结点的二叉树存在n-1条分支数,也就是n-1个非空的指针域。剩余的n+1个指针域都是NuLL。那么既然有这么多的空闲的指针,我们完全可以充分的利用它们。我们将空闲的指针域用来指向结点的前驱结点或后继结点,这样的二叉树称为线索二叉树。我们将一棵二叉树以某种遍历顺序将其变为线索二叉树的过程称为线索化
如下图的二叉树
这里写图片描述
这里写图片描述
这是一个中序线索化的例子,我们将空闲的左孩子指针(黑色弧线)用来指向结点的前驱,空闲的右孩子指针(红色弧线)用来指向结点的后继。
但现在出现了一个问题,我们如何知道一个指针指向的是孩子结点还是前驱后继结点??所以我们要对结点的结构做一些手脚

/*线索二叉树的结点结构定义*/
enum Tag
{
    child, thread  //       tag为child指向孩子结点,tag为thread指向线索结点
};
struct Node {
    char data;
    Tag lTag, rTag;
    Node *lchild, *rchild;
};

我们在结点结构中添加了lTag与rTag,分别标记左指针与右指针是孩子指针还是线索指针。
那么修改了结点的结构,我们将线索化实现为具体的代码。我们知道线索二叉树的目的是利用空闲的空指针,那么线索化的过程也就是修改空指阵的过程。某种次序的线索化的代码与遍历原理大致相同,如下的中序遍历依然只是将Operation()改成线索化操作

/*中序线索化操作--递归,类似中序遍历。pre始终指向上一个访问的结点*/
void ThrBinTree::Threading(Node *&BT)
{
    if (BT != NULL)
    {
        Threading(BT->lchild);  //  线索化左子树
        /*左孩子为空,则这个空指针可作为线索指针指向前驱结点*/
        if (BT->lchild == NULL)
        {
            BT->lTag = thread;
            BT->lchild = pre;
        }
        /*由于后继结点还没有访问到,所以当前访问的结点就作为pre的后继结点*/
        if (pre->rchild == NULL)
        {
            pre->rTag = thread;
            pre->rchild = BT;
        }
        pre = BT;   //  更新pre
        Threading(BT->rchild);  //  线索化右子树
    }
}
/*将树线索化,加上头结点*/
void ThrBinTree::InOrderThreading(Node *&h, Node *&BT)
{
    h = new Node;       //  生成头结点
    h->lTag = child;    //  头结点左孩子即是树的根结点,所以左标记为child
    h->rTag = thread;       //  头结点右孩子指向中序遍历的最后一个元素,是线索指针
    h->rchild = h;  //  初始化将h的右孩子回指
    if (!BT)    h->lchild = h;  //如果树为空,头结点左孩子也回指
    /*树不为空*/
    else
    {
        pre = h;    //  pre先指向h,这样前序遍历的第一个结点原本指向空,现在可以指向头结点
        h->lchild = BT; //根结点挂载到头结点的左子树
        Threading(BT);  //将树线索化
        pre->rchild = h;    //  线索化后pre指向中序遍历的最后一个结点,所以该结点的右孩子原本指空,现在作为线索结点指向头结点
        pre->rTag = thread;
        h->rchild = pre;    // 头结点的有孩子指向中序遍历的最后结点pre

    }
}

代码中添加了一个头结点,我们将头结点的左指针指向树的根结点,右指针指向遍历序列的最后一个结点,同时,我们将遍历序列的第一个结点的左指针与序列最后一个结点的右指针均指向头结点,这样的设计是我们既可以从序列第一个结点通过前驱线索遍历又可以从最后一个结点通过后继线索遍历。

/*中序线索遍历*/
void ThrBinTree::InOrderThrTraverse(Node *&BT)
{
    Node *p = BT->lchild;   //  头结点的右孩子为树的根结点
    while (p != BT) //如果p不等于BT,循环 当p==BT时,树为空树或者遍历完毕
    {
        while (p->lTag == child)    //  如果左标记为child,则一路指向左孩子
            p = p->lchild;
        cout << p->data << " "; // 访问到中序遍历的第一个元素
        while (p->rTag == thread&&p->rchild != BT)  //  结点为线索结点且结点右孩子不等于头结点
        {
            /*遍历线索路线*/
            p = p->rchild;
            cout << p->data << " ";
        }
        /*p的右标记是孩子标记,转到右孩子继续遍历 或者 p的右孩子指向头结点,p指向头结点,遍历结束*/
        p = p->rchild;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值