Bootstrap

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

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

前言

  前一篇简单的对二叉树进行初探,简单的了解了一下二叉树的一些概念,和二叉树的 顺序存储链式存储 以及二叉树的一些简单操作,和二叉树的几种遍历方式。这一篇,我们在对二叉树进行了解,假如这个二叉树有很多的叶子节点,那么叶子节点左孩子右孩子的指针空间是否会浪费呢?

1. 线索化二叉树初探

如开篇提到的,假如,一个二叉树有很多的叶子节点,如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o6VpMNw2-1588153397713)(https://user-gold-cdn.xitu.io/2020/4/29/171c512d6d915000?w=1038&h=375&f=png&s=43818)]
那么,上图中,标记 '^'的左孩子指针域和右孩子指针域中则为NULL,那么对于这些空间来说就是空间浪费

那么,当节点左孩子的指针域为空的时候,可以将这个指向遍历(以中序遍历为例)时的前一个节点,称之为前驱

节点右孩子的指针域为空的时候,可以将这个指向遍历(以中序遍历为例)时的后一个节点,称之为后继,如下图(以中序遍历为例):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5RqP6vVK-1588153397731)(https://user-gold-cdn.xitu.io/2020/4/29/171c51c37ef17ef4?w=1059&h=349&f=png&s=54901)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L7NJMRfO-1588153397741)(https://user-gold-cdn.xitu.io/2020/4/29/171c52b460797d51?w=1069&h=350&f=png&s=63529)]
我们称这种二叉树线索化二叉树

优点:

  • 节省空间
  • 遍历的时候查找方便

那么怎么区分节点左孩子指针指向的是左子树还是前驱呢?

那么我们可以对二叉树节点的结构进行优化,增加两个标识,来指示具体指向的是左右子树还是前驱后继,如下示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rvo1eRxL-1588153397751)(https://user-gold-cdn.xitu.io/2020/4/29/171c52b7f414fb7f?w=840&h=88&f=png&s=8092)]

// 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. 线索化二叉树实现

下面以中序遍历为例,线索二叉树的线索化和遍历

  • 线索二叉树的线索化

    首先,声明定义一些变量,并定义一个方法,

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSW 0
#define MAXSIZE 100  // 存储空间初始分配量

typedef int Status; // 函数类型
typedef char CElemType;

CElemType Nill = '#'; // 字符型以空格标识空

#pragma mark  -二叉树的构造
int indexs = 1;
typedef char String[24]; /*  0号单元存放串的长度 */
String str;
// 字符传构造字符数组
Status StrAssign(String T, char *chars) {
    int i;
    if (strlen(chars) > MAXSIZE) {
        return ERROR;
    }else {
        T[0] = strlen(chars);
        for (i = 1; i <= T[0]; i++) {
            T[i] = *(chars+i-1);
        }
        return OK;
    }
}

// 打印值
Status visit(CElemType e) {
    printf("%c  ", e);
    return OK;
}

按照中序遍历的方式,创建线索化二叉树,先创建,再线索化

// 中序二叉树线索化
// 定义全局变量,始终指向上一个访问的节点
BiThrTree pre;

void InThreading(BiThrTree p) {
    
    if (p) {
        // ✅ 递归左子树线索化
        InThreading(p->lchild);
        if (!p->lchild) { // ✅  没有左孩子,其前驱节点刚放访问过,赋值给 pre
            // 修改左标识
            p->LTag = Thread;
            // 左孩子的指针指向前驱
            p->lchild = pre;
        } else { // 有左孩子,修改左标识
            // 修改左标识
            p->LTag = Link;
        }
        
        // ✅ 判断前驱的右孩子
        if (!pre->rchild) { // 没有右孩子,p 就是 pre 的后继
            // 修改右标识
            pre->RTag = Thread;
            // 前驱右孩子指针指向后继(当前节点p)
            pre->rchild = p;
        } else {
            // 修改右标识
            pre->RTag = Link;
        }
        
        // ✅ 修改pre 指向 p的前驱
        pre = p;
        
        // ✅ 递归右子树线索化
        InThreading(p->rchild);
    }
}

在对二叉树进行线索化的时候,我们可以增加一个头节点

* 左孩子指向根节点
* 右孩子指向中序遍历的最后一个节点
* 中序遍历的第一个节点的左孩子指针(原来为null)和最后一个节点的右孩子指针(原来为null)都指向头结点

好处:可以从第一个节点起,顺着前驱遍历,也可以顺着后继遍历,不用开始就递归找左孩子,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LpPWVjzV-1588153397755)(https://user-gold-cdn.xitu.io/2020/4/29/171c53fb6a932ab3?w=756&h=351&f=png&s=52712)]

代码:

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 {
        // ✅ 图中1
        (*Thrt)->lchild = T;
        // 修改pre,指向上一个操作的节点
        pre = (*Thrt);
        
        //中序遍历进行中序线索化
        InThreading(T);
        
        //✅ 图中4,最后一个结点rchil 孩子指向头结点
        pre->rchild = *Thrt;
        
        //✅ 最后一个结点线索化
        pre->RTag = Thread;
        //✅ 图中2
        (*Thrt)->rchild = pre;
    }

    return OK;
}

  • 线索二叉树的遍历(中序遍历)
// 中序遍历
Status InOrderTraverse_Thr(BiThrTree T){
    
    BiThrTree p = T->lchild;
    // p = T时,说明根节点(首节点)为空或者遍历结束
    while (p != T) {
        // 遍历,找到开始的叶子节点,即找到左子树为空的节点
        while (p->LTag == Link) {
            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;
}

// 调用
BiThrTree H,T;
 
//StrAssign(str,"ABDH#K###E##CFI###G#J##");
StrAssign(str,"ABDH##I##EJ###CF##G##");
CreatBiThrTree(&T); /* 按前序产生二叉树 */
InOrderThreading(&H,T); /* 中序遍历,并中序线索化二叉树 */
InOrderTraverse_Thr(H);

;