如果构建平衡二叉树?
平衡二叉树顾名思义就是满足某种约束的二叉树。其构建过程自然也应该基于二叉树的基础之上做调整。根据前面的文章,我快速地构建一颗二叉树;然后,根据平衡因子调整二叉树为平衡二叉树。在调整过程中,如果左子树高了,可以右旋调整;如果右子树高了,可以左旋进行调整。下面我就开始探索平衡二叉树的构建过程。
1 平衡因子的计算方法
平衡因子是决定平衡二叉树如何调整的关键性指标,因此,搞清楚平衡因子的计算方法和含义至关重要。根据平衡二叉树的左右子树高度差不大于1的特性,我们可以快速地抽取出平衡因子的计算方法为:
平衡因子 = 左子树深度 - 右子树深度
由于做的减法,结果自然有正负之分。代码片段如下
当结果为正数时,代表左高右低,即左边子树深度大于右子树深度;当结果为负数时,代表左低右高,即右边子树的深度大于左边子树的深度。当平衡因子的绝对值大于等于2时,需要对子树进行旋转调整。
2 节点深度的计算方法
计算平衡因子时,我使用到了节点的深度。那么,结点的深度怎么计算呢?当新建节点时,节点的默认深度为1。如果目标节点为叶子节点,那么深度即为1;如果目标节点不是叶子节点,那么当前节点的深度取左右子节点深度中的最大值加1,可表示为
目标节点深度 = max(左节点深度,右节点深度) + 1
代码片段如下
上图中,max()方法负责计算出左右子树中最大的深度。calcDepth()方法中获取最大深度,然后在此基础上增加1,即为目标节点的深度。
3 子树旋转流程
为了使二叉树满足平衡二叉树的特性,当平衡因子的绝对值比1大时,则需要对子节点进行调整,使每个节点的平衡因子绝对值小于2。下面,我们就抽取局部子树来探索旋转流程。
3.1 左旋流程
当插入的大节点破坏了根节点的平衡时,根节点应该进行左旋转调整。调整过程如下图
上图中,当插入60节点时,破坏了40节点的平衡,40节点应该进行左旋调整。调整过程为:将右孩子节点置为父节点,图中即值50节点为原根节点40节点的父节点;如果40节点有父节点,则应该将父节点的后继指针指向50节点;如果50节点有左孩子结点,图中为45结点,则应该将左孩子节点置为原根节点40节点的右孩子。代码片段如下
当节点调整结束之后,需要重新计算原根节点和原右子节点的深度及平衡因子。
3.2 右旋流程
当插入的小节点破坏了根节点的平衡时,根节点应该进行右旋转调整。调整过程如下图
上图中,当插入20节点时,破坏了40节点的平衡,40节点应该进行右旋调整。调整过程为:将左孩子节点置为父节点,图中即将30节点置为原根节点40节点的父节点;如果40节点有父节点,则应该将父节点的后继指针指向30节点;如果30节点有右孩子节点,图中为35节点,则应该将右孩子节点置为原根节点40结点的左孩子。代码片段如下
和左旋一样,当节点调整完成之后,会重新计算原根节点和原左子节点的深度及平衡因子。
由于先左旋再右旋
和先右旋再左旋
两种情况可使用左右旋的原子函数进行组合,处于篇幅考虑,我这里就不做详细介绍了。下面我主要探索下构建平衡二叉树的主流程。上文中,我们知道构建平衡二叉树分两步
构建二叉树
按照平衡二叉树约束调整二叉树为平衡二叉树
第一步前面章节已做详细剖析。这里我主要探索二叉树到平衡二叉树的调整过程。调整代码片段如下
如上图,首先会计算当前节点的平衡因子,如果平衡因子大于1,左子树高,即应该进行右旋调整;如果平衡因子小于-1时,由于为负数,表示右子树高,应该进行左旋调整;结点调整结束之后,应该重新计算当前节点的平衡因子和深度。
为了显示地观察平衡二叉树的节点调整,你可以按层输出节点数据进行分析。当然了,如果你愿意,你也可以打个断点看看里面的内容,这更符合程序员的处事逻辑。平衡二叉树为了追求完美的平衡,会在增加或删除节点诱发节点不平衡时,进行多次的旋转调整,这开销是很大的。通常情况下,缺陷就是美是成立的。有时候适当的牺牲掉一些对完美的追求,可能会获得更好的效率、更高的性能,这就好比红黑树。下面的文章,我会继续探索红黑的构建过程及删除路程。