线索二叉树 Threaded BinaryTree

一、原理

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

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

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

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

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

结点结构重新定义如下:

lchild ltag data rtag rchild
  • ltag 为 0 时指向该结点的左孩子,为 1 时指向该结点的前驱;
  • rtag 为 0 时指向该结点的右孩子,为 1 时指向该结点的后继;
普通二叉树
线索二叉树 节点结构

二、源代码

/**
 * 线索化即在遍历过程中修改空指针
 * 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;
}
输入树的根结点: 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) 的条件控制已经不对了,线索化之后,不存在空结点了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • 四、树与二叉树 1. 二叉树的顺序存储结构 二叉树的顺序存储就是用数组存储二叉树。二叉树的每个结点在顺序存储中都有...
    MinoyJet阅读 1,487评论 0 7
  • 原链接:理解线索二叉树|CloudWong 线索二叉树原理 遍历二叉树的其实就是以一定规则将二叉树中的结点排列成一...
    简Cloud阅读 8,365评论 1 16
  • 二叉树是非线性结构,即每个数据结点至多只有一个前驱,但可以有多个后继。它可采用顺序存储结构和链式存储结构。 1、顺...
    文哥的学习日记阅读 1,859评论 0 3
  • 线索二叉树的原理 通过考察各种二叉链表,不管儿叉树的形态如何,空链域的个数总是多过非空链域的个数。准确的说,n各结...
    Gaizka阅读 542评论 0 0
  • 1.树(Tree): 树是 n(n>=0) 个结点的有限集。当 n=0 时称为空树。在任意一颗非空树中:有且仅有一...
    ql2012jz阅读 964评论 0 3