二叉树线索化的原理
二叉树是一种非线性结构,遍历二叉树几乎都是通过递归或者用栈辅助实现非递归的遍历。用二叉树作为存储结构时,取到一个节点,只能获取节点的左孩子和右孩子,不能直接得到节点的任一遍历序列的前驱或者后继。
为了保存这种在遍历中需要的信息,我们利用二叉树中指向左右子树的空指针来存放节点的前驱和后继信息
n个节点的二叉树中含有n+1个空指针域。利用二叉树中的空指针域 来存放在某种遍历次序下的前驱和后继 ,这种指针叫“线索”。这种加上了线索的二叉树称为线索二叉树。
根据线索的性质的不同,线索二叉树分为:前序线索二叉树 , 中序线索二叉树 , 后序线索二叉树
线索二叉树存储结构
/* 二叉树的二叉线索存储结构定义*/
typedef enum{Link, Thread}PointerTag; //Link = 0表示指向左右孩子指针;Thread = 1表示指向前驱或后继的线索
typedef struct BitNode
{
char data; //结点数据
struct BitNode *pLeft , *pRight; //左右孩子指针
PointerTag Ltag; //左右标志
PointerTag Rtag;
}BitNode, *BiTree;
二叉树的线索化过程
线索化的实质就是将二叉链表中的空指针改为指向前驱或后继的线索。由于前驱和后继信息只有在遍历该二叉树时才能得到,所以,线索化的过程就是在遍历的过程中修改空指针的过程。
先序
首先对于左子树存在的节点,就不会有前驱;同样,节点的右子树存在,那么不存在后继。那么我就先一直找寻节的左子树,判断左右子树是否为空 , 为空了才会考虑前驱和后继的问题。后继倒是很好找(反正要遍历二叉树),那么前驱的位置呢?如果遇到左子树不存在的节点,怎么才能找到已经已经扫面过得节点呢?所以我们需要创建一个节点指针,每次记住上一次扫描的节点 。 对于右子树就很好办了 , 如果当前节点的前一个节点不为空且右子树不存在,那么我们就放心大胆的链上吧 。然后循环得了。
BitNode* Prev =NULL//记录前一个节点的指针为
以这个二叉树为例,这个二叉树先序为12435。其中2的右节点为空,3的左节点为空,45左右节点都为空。右节点为空,需要后继,左节点为空需要前驱。
每次只会把当前节点的左子树前驱链上,这一次的 后继 不会在本次链上,当pCur指向下一个节点的时候,才会把上一次的后继链上
代码
void _PreOrderThreading(BitNode *& Root)
{
if (Root == NULL)
{
return;
}
if (Root->pLeft == NULL) //没有左子树
{
Root->pLeft = Prev; //前驱
Root->Ltag = Thread;
}
if (Prev != NULL && Prev->pRight == NULL) // 上一个节点有没有 右子树
{ //Prev第一次进来 为空
Prev->pRight = Root; //后继
Prev->Rtag = Thread;
}
Prev = Root;//前驱 , 每次记住上次的节点
//判断Root是否有左右孩子
if (Root->Ltag == Link)
_PreOrderThreading(Root->pLeft);
if (Root->Rtag == Link)
_PreOrderThreading(Root->pRight);
}
个人理解
线索二叉树更像是把一棵树存在一个双向链表里面,让每一个节点按顺序(先序,中序,后序)链接,左节点指向前一个节点,右节点指向后一个节点。所以最好的办法是先把各个节点的顺序搞清楚,然后用链表放进去,互相指向就可以了
我这里只写了前序的线索化,中序和后序是差不多的。可以参考下面几位大佬的博客。
二叉树线索化以及线索化的先序、中序、后序遍历
图解中序遍历的线索二叉树
彻底理解线索二叉树