何谓二叉树
二叉树(binary tree)是指树中节点的度不大于2的有序树。
作为树的一种,相较于其他的树,二叉树在存储结构和算法上较为简单,并且一般树可以轻松转化为二叉树,因而备受重视。
线索二叉树的诞生——中序序列
在了解线索和线索二叉树前,我们或许应该了解线索二叉树是为了解决什么诞生。
首先,在这里,我们先建立一个简单的二叉树作为讨论的参考对象,用ABCDEF建立一个完全二叉树。
现在,我们需要寻找该二叉树的 中序序列 中某个节点的前驱。假设,我们要找的是A点的前驱吧。我们可以迅速的找到E。这个一眼就能看出。
如果现在我们把这个二叉树扩展到深度N=非常大的数,比如10000。现在,让你找出A的前驱和后驱,那么我们需要花费多少时间呢?
答案是很快,因为这棵树现在还是完全二叉树呢,哈哈。不过如果不是完全二叉树呢?
是的,或许就像部分朋友想到的那样,我们需要对整个树去进行一次遍历,假设这个树的深度是10000,那我们就需要访问10000个节点才能保证一定能找到A的前驱是谁。
事实上,我们实际问题中的二叉树,可能层数会比这更深,如果当一项任务需要大量访问节点的前驱后时,它对内存的开销,必然是巨大的。
而线索化,就是为了解决该问题诞生。
线索的价值
我们现在知道了,在寻找节点前驱后继时,我们将面临巨大的麻烦。而这的源头跟二叉树本身的非线性结构有关。
那么我们应该如何处理它呢?
如果你的机子硬盘只用了10%,但是你的运行内存已经被你的任务跑满的时候,你会怎么做呢?我们可以让硬盘的一部分,作为虚拟内存为你工作,为悲惨的运行内存分担一份压力,对吧?
没错,如果我们在寻找前驱后继这件事上,运行时间花费代价过大,我们可以适当牺牲地一小部分存储空间,来帮我们处理这个问题。
前驱后继,说到底,是一种线性结构,而我们正好有接触过这样一种线性结构,它的数据存储结构并非连续的。
对,链表,如果我们以链表的方式,为每个节点添加他的前驱后继节点的地址,我们只需花小小的代价获得巨大的效率提升。
线索的建立
线索的建立方式如下:
如果某结点的左子树为空,则该结点的左孩子指针指向其前驱结点。
如果某结点的右子树为空,则该结点的右孩子指针指向其后继结点。
如此建立的指针便是我们所说的线索。
由于节点的建立方式是将本来的空指针转为填入一个前驱或后继的指针。因此在这一步,实际的空间代价几乎为0。
线索的问题
这时候只要仔细一想,我们便会发现线索的引入带来了另一个问题:我怎么知道这个指针,指的是线索还是子树呢?
于是,我们采用了最简单的办法:标志位。
我们这样定义该标志位的规则:
当标志位tag=0,它表示该指针指向子树
当标志位tag=1,他表示该指针指向前驱或后继
在添加完标志位后,我们的节点结构便确定下来了:
相较于最初的节点,添加两个标志位。
总体的空间代价便是2个标志位 * 节点数。
线索化二叉树节点结构
简单的线索二叉树构成
// 节点数据 TelemType
typedef struct TelemType;
// 节点结构
typedef struct TreNode{
TelemType data;
TreNode *lchild, *rchild;
Tag lTag ,rTag;
}
线索二叉树建立
void inThreading(ThrBiTree T, ThrBiTree &pre){
if(T){
inThreading(T->lchild, pre);//左子树线索化
if(!T->lchild){//当前结点的左孩子为空
T->lTag = Thread;
T->lchild = pre;
}else{
T->lTag = Link;
}
if(!pre->rchild){//前驱结点的右孩子为空
pre->rTag = Thread;
pre->rchild = T;
}else{
pre->rTag = Link;
}
pre = T;
inThreading(T->rchild, pre);//右子树线索化
}
总结
线索二叉树,是一种用空间置换时间的一种算法。它通过让二叉树在本来的非线性结构上增加了一层线性结构,来提高二叉树在前驱后继查询的效率。它的建立最好是在最开始建立树的时候便建立。
这是我第一次在CSDN上发布文章,如果有任何意见,见解或是其中有任何错误,欢迎各位大佬指点一二。