线索二叉树的线索化及遍历

摘要

  • 定义和数据结构
  • 线索化
  • 遍历

定义和数据结构

  1. 定义
    结点结构中含有线索的的二叉树称为线索二叉树。
    若结点有左子树,则其lchild域指示其左子,否则令lchild域指示其前驱;若结点有右子树,则其rchild域指示其右子,否则令rchild域指示其后继。
    为了避免混淆,需要增加两个标志域LTag和RTag,标志域为0表示结点后接指针,标志位为1表示结点后接线索。

  2. 数据结构

    typedef struct BiThrNode
    {
        ElemType data;
        struct BiThrNode *lchild, *rchild;
        PointerTag LTag, RTag;
    } BiThrNode, *BiThrTree;
    

    其中,PointerTag的定义为

    typedef enum PointerTag
    {
        Link,
        Thread
    } PointerTag;
    

    Link == 0 表示指针,Thread == 1表示线索。

线索化

  线索化的实质是将二叉链表中的空指针改为指向前驱或后继的线索,而前驱或后继的信息只能在遍历时得到,所以线索化的过程即为在遍历过程中修改空指针的过程。所以为了记录遍历过程中访问结点的先后关系,附设一个指针pre始终指向刚刚访问过的结点,即若指针p指向当前访问的结点,则pre指向它的刚访问过的结点。

void InThreading(BiThrTree p, BiThrTree *pre)
{
    if (p)
    {
        // 左子树线索化.
        InThreading(p->lchild, pre);

        // 前驱线索.
        // 若当前结点无左子,则修改标志域并使得 lchild 指向 p 的前驱.
        if (!(p->lchild))
        {
            p->LTag = Thread;
            p->lchild = (*pre);
        }

        // 后继线索.
        if (!((*pre)->rchild))
        {
            (*pre)->RTag = Thread;

            // 将前驱结点的后继结点设置为当前结点.
            (*pre)->rchild = p;
        }

        // 更新 pre, 使其始终指向刚访问过的结点.
        (*pre) = p;

        // 右子树线索化.
        InThreading(p->rchild ,pre);
    }

    return;
} // InThreading

  我这里在学习的时候遇到一个问题:刚访问过的结点pre的后继线索设置失败。书中的伪代码未说明pre是否为全局变量,而数据结构中也没有存储pre,所以我先将pre作为一个局部变量定义并值传递给了InThreading,乍一看没什么问题,但是仔细debug之后发现,在调用这个递归形式的算法时,系统会维护一个函数调用栈,每进入一层新的递归,系统就会保存当前调用的返回地址,并压入新的调用的返回地址。在执行完子函数后,子函数的返回地址会被弹出栈,此时,之前做的一切修改就都不见了,pre也不会得到更新。

  改进方法有两个:
    1. 将pre设置为全局变量;
    2. 将值传递改为指针传递。
  本文采用了第二种方法。

遍历

  线索化之后,可以很方便地得到结点在遍历所得线性序列中的前驱和后继,也可以很方便地得到线性序列。

Status InOrderTraverse_Thr(BiThrTree T, Status (*Visit)(ElemType e))
{
    // p 指向根结点.
    BiThrNode *p = T->lchild;

    // 空树或遍历结束时.
    while (p != T)
    {
        while (p->LTag != Thread)
        {
            p = p->lchild;
        }

        if (!Visit(p->data))
        {
            return ERROR;
        }

        // 访问后继结点.
        // 找到最左叶结点, 就可以轻松地访问其后继结点.
        while ((p->RTag == Thread) && (p->rchild != T))
        {
            p = p->rchild;
            Visit(p->data);
        }

        p = p->rchild;
    }

    return OK;
} // InOrderTraverse_Thr

参考文献

  1. 数据结构:C语言版/严蔚敏,吴伟民编著. ——北京:清华大学出版社,2007
  2. 线索二叉树详解 https://blog.csdn.net/s_999999/article/details/86157532
  3. 线索二叉树原理及前序、中序线索化(Java版)https://blog.csdn.net/UncleMing5371/article/details/54176252
  4. 关于函数调用栈(call stack)的个人理解 https://blog.csdn.net/VarusK/article/details/83031643

猜你喜欢

转载自www.cnblogs.com/tianshihao/p/12663611.html