一、二叉树线索化
对于一棵普通的二叉树,它的节点结构需要由两个指针域和一个数据域构成。而一棵树中必定存在一些指针域没有被使用到,这就造成了空间的浪费。

另一方面,我们经常用到二叉树的前、中、后序遍历,如果想求某种遍历中某个节点的前驱和后继节点,那就需要重新进行遍历。这无疑会造成时间的浪费。
所以为什么不把空闲的指针域利用起来,让其指向节点的前驱或后继呢?其实这就是线索二叉树的核心思想。
我们以中序遍历为例进行讲解。对于前面那棵二叉树,我们很容易得出它的中序遍历结果为:42513。但对于计算机来说,它只知道当前遍历到的节点cur,以及我们可以缓存下来的前一个遍历到的节点pre。
于是,计算机首先遍历到「4」这个节点

显然,这个节点的左右指针是空的,这两个指针域可以利用起来。但这个节点并没有前驱节点,而后继节点计算机并不知道是谁,所以这两个指针域还是只能指向空。
接下来遍历到「2」这个节点

虽然「2」节点的左右指针都不是空的,但它的前驱节点「4」的右指针还没有指向后继节点。而「4」的后继节点正是「2」节点。所以我们将「4」的右指针指向「2」

接下来指针遍历到了「5」节点

此时「5」节点的左指针为空,而且它的前驱我们知道是「2」,所以将左指针指向「2」

接下来指针遍历到「1」节点,它的前驱节点「5」的右指针为空,所以将「5」的右指针指向「1」

最后,指针遍历到「3」,它的左指针为空,所以将左指针指向「1」

至此,线索二叉树就构建完成了。但对于计算机来说,并不知道哪些指针是指向前驱或后继的指针,哪些指针是指向左右孩子的指针。所以我们还需要在二叉树节点的结构中引入两个bool变量,区分该指针是否是线索。
public class ThreadedBinaryTreeNode<T>
{
public T data;
public ThreadedBinaryTreeNode<T> left;
public ThreadedBinaryTreeNode<T> right;
public bool leftTag;
public bool rightTag;
}
线索化的代码如下:
public void InThreading()
{
InThreadingMethod(Head);
// 处理遍历的最后一个节点
if (_pre != null) _pre.RightTag = true;
}
private ThreadedBinaryTreeNode<T>? _pre;
private void InThreadingMethod(ThreadedBinaryTreeNode<T>? head)
{
if(head == null) return;
InThreadingMethod(head.Left);
// 前驱线索
if (head.Left == null)
{
head.Left = _pre;
head.LeftTag = true;
}
// 后继线索
if (_pre != null && _pre.Right == null)
{
_pre.Right = head;
_pre.RightTag = true;
}
_pre = head;
InThreadingMethod(head.Right);
}
完成线索化后,当我们需要查询某个节点的后继时,如果它有后继指针,那就可以直接返回后继指针指向的节点;否则就返回其右子树按中序遍历的第一个节点(最左节点)
public ThreadedBinaryTreeNode<T>? GetNext

本文详细介绍了二叉树线索化的过程和作用,通过中序遍历为例展示了如何构建线索二叉树,并提供了寻找节点前驱和后继的代码。接着,文章探讨了Morris遍历,这是一种不预先进行线索化的遍历方法,空间复杂度为O(1),时间复杂度为O(N)。通过对Morris遍历的原理和过程的解析,包括前序、中序和后序遍历的实现,读者可以深入理解这一高效的数据结构操作技巧。
最低0.47元/天 解锁文章
157

被折叠的 条评论
为什么被折叠?



