数据结构与算法(十一) -- 线索二叉树

320 阅读4分钟

一、线索二叉树

当我在使用二叉树的时候(如图), 每一个节点都会对应一个左右孩子节点, 但是此时的叶子节点没有孩子, 就会让这几个空间浪费掉. 这不是一个好现象, 我们需要将这个空间给利用起来.

当我们在做种序遍历的时候, 得到HDIBJEAFCG 这样的字符序号. 遍历过后可以得知, H的后继是D, I的前驱是D后继是B等. 也就是遍历过后, 我们知道每一个节点的前驱后继分别是谁. 在二叉链表中, 我们只知道每一个节点的孩子是谁, 要想知道前驱后继, 必须遍历一次. 我们可以考虑将前驱后继在遍历的时候就保存起来, 不用每一次查找前驱后继就需要遍历.

我们可以考虑用一些叶子节点的空闲空间, 来记录这个前驱后继.如图, 将所有节点的空左孩子区域改成存储这个节点的前驱继. 空右孩子改成存储这个节点的后继

所以线索二叉树等于是把一棵二叉树转变成了一个双向链表. 为我们的插入、查找等带来了很大的方便. 所以对二叉树以某种次序遍历使其变为线索二叉树的过程称之为线索化.

此时问题还没有解决完成, 因为我们不知道左孩子究竟是前驱还是实际的左孩子. 因此我们还需要再增加两个标记来记录当前的左孩子究竟是左孩子还是前驱.

当标记为0代表这个位置是孩子位置, 为1则为前驱或者后继

二、代码实现线索二叉树

2.1、线索二叉树结构

定义一个线索二叉树的存储结构:

typedef int Status;//
typedef int TElemType;//数据类型
typedef enum {Link, Thread} PointerTag;//标记的类型
typedef struct BithrNode{
    TElemType data;//数据
    struct BithrNode *lChild, *rChild;//左右孩子指针
    PointerTag ltag, rtag;//左标记 右标记
} BiThrNode, *BiThrTree;

2.1、线索二叉树的转化

线索化就是将空的左右孩子转化为前驱后继. 为了方便处理, 在第一个节点之上我们再定一个头节点, 用来指向遍历的头尾

BiThrTree pre = NULL;//记录上次访问的节点
void InThreading(BiThrTree p) {
    if (p) {
        InThreading(p->lChild);
        if (!p->lChild) {
            p->ltag = Thread;
            p->lChild = pre;
        } else {
            p->ltag = Link;
        }
        if (!pre->rChild) {
            pre->rtag = Thread;
            pre->rChild = p;
        } else {
            pre->rtag = Link;
        }
        pre = p;
        InThreading(p->rChild);
    }
}

除去中间的判断处理, 几乎与遍历一致. 中间的主要是处理当左右孩子不能存在的时候, 设置好标记, 代表这个左右孩子不存在, 这个左右孩子现在代表的是前驱后继.

首先先看当前节点p是否有左孩子, 没有则标记线索指向前驱, 前驱为上次访问的节点pre. 再处理上一个节点pre是否有右孩子, 没有就标记线索指向后继, 也就是当前的节点p

例如: p为H节点, 此时pre为头节点, H节点没有左孩子, 即左孩子指向头节点pre. pre头节点有右孩子指向G节点(末尾). 此时将pre设置为H节点, 递归H节点的右孩子(为空). 接着执行D节点的左孩子处理, 此时p为D节点, pre为H节点.

具体的线索化实现了, 我们需要将二叉树转化为线索二叉树结构, 然后进行线索化, 在线索二叉树中, 头节点用来记录中序遍历的头和尾:

/* 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点 */
Status InOrderThreading(BiThrTree *Thrt , BiThrTree T){
    *Thrt=(BiThrTree)malloc(sizeof(BiThrNode));
    if (! *Thrt) {
        exit(OVERFLOW);
    }
    //建立头结点;
    (*Thrt)->ltag = Link;
    (*Thrt)->rtag = Thread;
    //右指针回指向
    (*Thrt)->rChild = (*Thrt);
    
    /* 若二叉树空,则左指针回指 */
    if (!T) {
        (*Thrt)->lChild=*Thrt;
    }else{
        
        (*Thrt)->lChild=T;
        pre=(*Thrt);
        
        //中序遍历进行中序线索化
        InThreading(T);
        
        //最后一个结点rchil 孩子
        pre->rChild = *Thrt;
        //最后一个结点线索化
        pre->rtag = Thread;
        (*Thrt)->rChild = pre;
        
    }
    return 1;
}

2.3、线索二叉树遍历

Status InOrderTraverse_Thr(BiThrTree T){
    BiThrTree p;
    p = T->lChild;//指向根节点
    while (p != T) {
        
        while (p->ltag == Link) {
            p = p->lChild;
        }
        printf("%d ", p->data);
        while (p->rtag == Thread && p->rChild != T) {
            p = p->rChild;
            printf("%d ", p->data);
        }
        
        p = p->rChild;
    }
    return 1;
}

  1. 首先将p指向第一个节点A开始遍历.
  2. 第一个while循环从第一个节点A开始向下
  3. 第二个while循环找到开始打印的第一个节点H
  4. 第三个while循环处理H右线索是否存在, 遍历右线索(后继)找到D打印. D有孩子结束while, p指向D的右孩子I节点
  5. 重复循环找到所有节点