线索二叉树 Threaded BinaryTree

一、原理

利用二叉树中闲置的 n+1 个空间记录每个节点的前驱和后继。

线索:利用二叉树空链域存放在某种遍历次序下结点的前驱和后继,这些指针称为线索,加上线索的二叉树称为线索二叉树。根据线索性质的不同,线索二叉树可分为前序、中序、后序三种。
线索化的过程就是在遍历的过程中修改空指针的过程。

记 ptr 指向二叉链表中的一个结点,以下是建立中序线索的规则:

  • ptr->lchild 为空,则存放该结点的前驱结点,这个结点称为 ptr 的中序前驱;
  • ptr->rchild 为空,则存放该结点的后继结点,这个结点称为 ptr 的中序后继;

显然,在决定 lchild 是指向左孩子还是前驱,rchild 是指向右孩子还是后继,需要一个区分标志的。因此,我们在每个结点再增设两个标志域 ltag 和 rtag,注意 ltag 和 rtag 只是区分0或1数字的布尔型变量,其占用内存空间要小于像 lchild 和 rchild 的指针变量。

结点结构重新定义如下:

lchildltagdatartagrchild
  • ltag 为 0 时指向该结点的左孩子,为 1 时指向该结点的前驱;
  • rtag 为 0 时指向该结点的右孩子,为 1 时指向该结点的后继;

《线索二叉树 Threaded BinaryTree》 普通二叉树
《线索二叉树 Threaded BinaryTree》 线索二叉树 节点结构

二、源代码

/**
 * 线索化即在遍历过程中修改空指针
 * n 个结点的二叉树一共有 2n 个指针,n-1 已经用于 link,剩下 n+1 个用于 thread
 * 剩下的:n-1 个用于按照遍历的顺序联系结点,多出的 2 个用于指向给出的头结点
 */
#include <iostream>

using namespace std;

typedef enum {
    link, thread // 线索存储标志位:link(0),表示指向左右指针,thread(1),表示指向前趋后继的线索
} tag;

typedef struct Bnode {
    char data;
    struct Bnode *Lchild, *Rchild;
    tag Ltag, Rtag; // 多了2个标志位
} Bnode, *Bintree;

Bnode *pre; // 全局变量,始终指向刚刚访问过的结点

/**
 * 先序建立二叉树 TLR
 * @param T 引用 main 函数中定义的树根结点的地址
 */
void createBintree(Bintree &T) {
    char ch;
    cin >> ch;
    getchar(); // 接收 space 或 enter

    if (ch == '#') { // # 结束符
        T = NULL;
    } else {
        T = (Bintree) malloc(sizeof(Bnode)); // 新建结点
        T->data = ch;
        cout << ch << " 左结点(#结束): ";
        createBintree(T->Lchild);
        cout << ch << " 右结点(#结束): ";
        createBintree(T->Rchild);
    }
}

/**
 * 在中序遍历基础上,线索化联系结点,n-1
 */
void inThread(Bintree T) {
    if (T) {
        inThread(T->Lchild); // 左子树线索化

        // 原本中序遍历输出数据的地方 变为修改空指针
        // 线索化结点的两种方式:前趋,后继
        if (!T->Lchild) {
            T->Ltag = thread;   // 前趋线索
            T->Lchild = pre;    // 当前结点T 找前趋pre
        }
        if (!pre->Rchild) {
            pre->Rtag = thread; // 后继线索
            pre->Rchild = T;    // pre结点找后继T (T在递归遍历过程中可以是树上任意一个结点)
        }
        pre = T; // 始终指向刚刚访问过的结点 即下一轮当前结点的上一个结点

        inThread(T->Rchild); //递归右孩子线索化
    }
}

/**
 * 完整化线索二叉树 添加头结点 设置结束结点指向
 * @param head 线索二叉树的头结点
 * @param T 原本的二叉树
 */
void inThreadTree(Bnode *head, Bintree T) {
    if (!T) {
        // 若二叉树为空,则将Lchild和Rchild指向自己(双向链表head前趋和后继指向自己,链表为空)
        head->Lchild = head;
        head->Ltag = link;
        head->Rchild = head;
        head->Rtag = link;
    } else {
        // 起始工作
        head->Lchild = T;       // head左指针记录下树根,这样在inThreadTraverse方法中只需要传入head就够了
        head->Ltag = link;
        head->Rchild = head;    // head右指针一定会线索化,先赋初值指向自己(head)
        head->Rtag = thread;
        pre = head;             // pre 赋初值,指向head,然后中序线索化

        // 中序线索化
        inThread(T);

        // 收尾工作
        pre->Rtag = thread;     // 线索化之后,pre是遍历的最后一个结点
        pre->Rchild = head;
        head->Rchild = pre;     // 在起始工作中,先给了head->Rchild一个初值,这里赋给了真正的值
    }
}

/**
 * 中序遍历线索二叉树
 * @param head 线索二叉树的头结点
 */
void inThreadTraverse(Bintree head) {
    Bnode *p = head->Lchild;    // 起始工作中设置了head->Lchild为树根

    // 1.满足树为空的情况,树空时设置了head左右孩子都指向自己
    // 2.满足循环终结条件,收尾工作中设置了最后一个节点的Rchild为head
    while (p != head) {

        // 寻找线索开头 从根寻找到最左结点
        while (p->Ltag == link) {
            p = p->Lchild;
        }
        cout << p->data << " ";

        // 根据线索,用链表的方式遍历
        while (p->Rtag == thread && p->Rchild != head) {
            p = p->Rchild;
            cout << p->data << " ";
        } // 该循环一般在 p->Rtag == link 时跳出,说明右边是一棵树,需要重新寻找右子树的最左节点

        p = p->Rchild;
    }
}

/**
 * 普通中序遍历
 */
void inOrder(Bintree T) {
    if (T) {
        inOrder(T->Lchild);
        cout << T->data << " ";
        inOrder(T->Rchild);
    }
}

int main() {

    // 创建树
    Bintree bintree;
    cout << "输入树的根结点: ";
    createBintree(bintree);
    cout << endl;

    // 普通中序遍历
    cout << "普通中序遍历" << endl;
    inOrder(bintree);
    cout << endl << endl;

    // 线索二叉树并遍历
    Bnode *head = (Bnode *) malloc(sizeof(Bnode)); // 头结点
    inThreadTree(head, bintree);
    cout << "线索化后中序遍历" << endl;
    inThreadTraverse(head);
    cout << endl << endl;

    return 0;
}

《线索二叉树 Threaded BinaryTree》

输入树的根结点: 1
1 左结点(#结束): 2
2 左结点(#结束): #
2 右结点(#结束): #
1 右结点(#结束): 3
3 左结点(#结束): 4
4 左结点(#结束): #
4 右结点(#结束): #
3 右结点(#结束): 5
5 左结点(#结束): #
5 右结点(#结束): #

普通中序遍历
2 1 4 3 5 

线索化后中序遍历
2 1 4 3 5 

误区

本来想用做个尝试,在线索化二叉树后,随便取一个结点,输出其前趋和后继。

原本采用先序遍历的方式寻找:

void preOrderSearch(Bintree T, char target) {
    if (T) {
        if (T->data == target) {
            if (T->Ltag == thread) cout << "前趋结点:" << T->Lchild->data << endl;
            if (T->Rtag == thread) cout << "后继结点:" << T->Rchild->data << endl;
            return;
        }
        preOrderSearch(T->Lchild, target);
        preOrderSearch(T->Rchild, target);
    }
}

错误点:if (T) 的条件控制已经不对了,线索化之后,不存在空结点了。

    原文作者:谢小帅
    原文地址: https://www.jianshu.com/p/333ed20f087c
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞