数据结构与算法——线索二叉树

531 阅读4分钟

1.线索二叉树

每一棵二叉树上,很多结点都含有未使用的指向NULL的指针域。除了度为2的结点,度为 1 的结点,有一个空的指针域;叶子结点两个指针域都为NULL。
在有 n 个结点的二叉链表中必定存在 n+1 个空指针域。

线索二叉树实际上就是使用这些空指针域来存储结点之间前趋和后继关系的一种特殊的二叉树。

线索二叉树中,如果结点有左子树,则 lchild 指针域指向左孩子,否则 lchild 指针域指向该结点的直接前趋;同样,如果结点有右子树,则 rchild 指针域指向右孩子,否则 rchild 指针域指向该结点的直接后继。

为了避免指针域指向的结点的意义混淆,需要改变结点本身的结构,增加两个标志域。

LTag 和 RTag 为标志域。实际上就是两个布尔类型的变量:
LTag 值为 0 时,表示 lchild 指针域指向的是该结点的左孩子;为 1 时,表示指向的是该结点的直接前趋结点;
RTag 值为 0 时,表示 rchild 指针域指向的是该结点的右孩子;为 1 时,表示指向的是该结点的直接后继结点。 代码结构如下:

/* Link==0表示指向左右孩子指针, */
/* Thread==1表示指向前驱或后继的线索 */
typedef enum {Link,Thread} PointerTag;

/* 线索二叉树存储结点结构*/
typedef struct BiThrNode{
    
    //数据
    CElemType data;
    
    //左右孩子指针
    struct BiThrNode *lchild,*rchild;
    
    //左右标记
    PointerTag LTag;
    PointerTag RTag;
    
}BiThrNode,*BiThrTree;

2.对二叉树进行线索化

将二叉树转化为线索二叉树,实质上是在遍历二叉树的过程中,将二叉链表中的空指针改为指向直接前趋或者直接后继的线索。 线索化的过程即为在遍历的过程中修改空指针的过程。

在遍历过程中,如果当前结点没有左孩子,需要将该结点的 lchild 指针指向遍历过程中的前一个结点,所以在遍历过程中,设置一个指针(名为 pre ),时刻指向当前访问结点的前一个结点。

BiThrTree pre; /* 全局变量,始终指向刚刚访问过的结点 */
/* 中序遍历进行中序线索化*/
void InThreading(BiThrTree p){
    if (p) {
        //递归左子树线索化
        InThreading(p->lchild);
        //无左孩子
        if (!p->lchild) {
            //左孩子指针指向前驱
            p->lchild = pre;
            //前驱线索
            p->LTag = Thread;
        } else {
            p->LTag = Link;
        }
        
        //前驱没有右孩子
        if (!pre->rchild) {
            //后继线索
            pre->RTag = Thread;
            //前驱右孩子指针指向后继(当前结点p)
            pre->rchild = p;
        } else {
            pre->RTag = Link;
        }
        
        //保持pre指向p的前驱
        pre = p;
        //递归右子树线索化
        InThreading(p->rchild);
    }
}

3.加入头结点的线索二叉树

线索化的二叉树,在遍历的时候,就等价于操作一个双向链表结构。为了形成双向链表结构,我们在二叉线索树链表上加入一个头结点。

  • 头结点的左孩子指向根结点。
  • 头结点的右孩子指向中序遍历的最后一个结点。
  • 使⼆叉树中序列中的第一个结点中,lchild 域指针和最后一个结点的rchild 域指针均指向头结点 这样做的好处是:可以从第⼀个结点起顺着后继进⾏遍历,也可以从最后⼀个结点起顺着前驱进⾏遍历。 代码实现:
/* 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点 */
Status InOrderThreading(BiThrTree *Thrt , BiThrTree T){
    //创建头结点
    *Thrt = (BiThrNode *)malloc(sizeof(Thrt));
    if (!*Thrt) {
        exit(OVERFLOW);
    }
    
    //建立头结点
    (*Thrt)->LTag = Link;
    (*Thrt)->RTag = Thread;
    
    //右指针先指回自己
    (*Thrt)->rchild = *Thrt;
    
    //若T为空 则左指针指回自己
    if(!T) {
        (*Thrt)->lchild = *Thrt;
    } else {
        (*Thrt)->lchild = T;
        pre = *Thrt;
        
        //中序遍历进行中序线索化
        InThreading(T);
        
        //最后一个结点右孩子指向 thrt
        pre->rchild = *Thrt;
        //最后一个结点线索化
        pre->RTag = Thread;
        
        //Thrt右孩子指向最后一个结点
        (*Thrt)->rchild = pre;
    }
    return OK;
}

4.遍历线索二叉树

遍历线索二叉树时,可以先根据头结点找到二叉树的根节点;
再循环查找左子树,找到中序遍历时的第一个结点,开始遍历; 如果遇到右子树标记为1,并且不是最后一个结点,找到其后继结点,取值; 按照中序遍历规则,继续找到右子树的最左结点进行遍历。

//中序遍历线索二叉树
Status InOrderTraverse_Thr(BiThrTree T){
    BiThrTree p;
    //p指向根结点
    p=T->lchild;
    
    //空树或遍历结束时,p==T
    while(p!=T) {
        //找到遍历的起始位置
        while (p->LTag == Link) {
            p = p->lchild;
        }
        printf("%c ",p->data);
        
        //当结点右标志位为1时,直接找到其后继结点 进行取值
        while (p->RTag == Thread && p->rchild != T) {
            p = p->rchild;
            printf("%c ",p->data);
        }
        //继续
        p=p->rchild;
    }
    
    return OK;
}