平衡二叉树,又称AVL树。是一种高度平衡的二叉排序树。这里的”高度“ 大家理解为 height。也就是节点的左子树和右子树的高度差绝对值不超过1。
重点一:高度平衡
指的是 节点 左子树高 与 右子树高,差值绝对值不超过1。
重点二:AVL的插入函数
先按照二叉树原理插入节点,然后再看当前树是否平衡。
******* 一定要自己写一遍 ,重点看插入函数********
https://github.com/ai2101039/AVLTree
先上一下结果图
好了,开始一步步的写代码。
大家要是了解了原理,其实很简单。真的很简单。
(tree 为树中任意一节点,K1为其子树,K2为插入的节点)
图1:tree 的 左节点 K1,插入K2为K1左孩子,导致Tree节点不平衡。针对这种节点形式,我们使用函数
left_left_rotation(tree);
图2:tree 的 左节点 K1,插入K2为K1右孩子,导致Tree节点不平衡。
left_right_rotation(tree);
图3:tree 的 右节点K1,插入K2为K1右孩子,导致Tree节点不平衡。
right_right_rotation(tree);
图4:tree 的 右节点K1,插入K2为K1左孩子,导致Tree节点不平衡。
right_left_rotation(tree);
重点:这里我们需要记住,函数名称的命名,指的是节点虚拟关系符合如图展示。
为了方式大家被绕晕,我需要解释一波
left_left_rotation(tree); 针对图1展现样式。
那么这时候的旋转 叫做 右旋,即tree 围绕K1,往右旋转,即顺时针旋转。
left_right_rotation(tree); 针对图2展现样式。
这时候不能简单的进行 右旋转,需要先判断 K1 与 K2的关系,这步骤很重要, 在后续函数里面也会做这一步判断。此时可以发现,Tree 与 K1子树 属于 左左样式,而K1与K2属于右右关系,所以需要先将K1进行左旋转,然后再将Tree进行右旋转。
right_right_rotation(tree); 针对图3展现样式。
那么这时候的旋转 叫做 左旋,即tree 围绕K1,往左旋转,即逆时针旋转。
right_left_rotation(tree); 针对图1展现样式。
和图2类似,这时候我们先判断了,tree 与 K1 的样式,符合右右关系,但是K1 与K2 符合 左左关系,这时候需要先将K1进行右旋转,再将Tree进行左旋转。
到了这一步是否还有模糊的地方,没关系,我之前也是有,但是自从我写了一遍代码我就知道,其实很简单。最主要就是一个。
递归思想
我们来看代码
先看插入代码,再看旋转代码。
插入
Node基类,其中Key 是继承自 Comparable,其实很好理解,Node得需要排序。
class BaseNode<T extends Comparable> {
BaseNode left;
BaseNode right;
int height;
T key;
}
我尽量把每一行的解释写的清楚,大家一定要用递归的想法去理解。
/**
* 插入
*
* @param root 需要和Node进行比较的根节点
* @param node 需要插入的节点
* @return
*/
private BaseNode insertNode(BaseNode root, BaseNode node) {
if (root != null) {
/*
* 如果root不为空,那么就需要比较 root 和 node 的大小
* 以便于知道 node下一步 是和 root 的左子树做比较 还是右子树比较
* 这里我们举个例子,如果 node 应该插入 root 的左侧,是不是下一步应该 root.left和node 做比较
* 而如果 root.left 如果是null,那么 是不是 root.left = node,也就是把node赋值给 root.left
*/
//比较 node 和 root 中 key 的值,这里的返回值是我们 实现接口Comparable,去做的,参考 AVLNode.AVLComparable
int cmp = node.key.compareTo(root.key);
//cmp < 0,说明root的key值小于node的key值,node 插入到 root 的左子树,也就是node 要和 root.left做比较
if (cmp < 0) {
/*
* 这里就开始使用递归了,同时需要看到有赋值的动作
* 这里需要好好理解一下,递归的核心思想是往能终结递归运算的方向进行,那我们想,如何才能结束
* 只有当root 为null 时,这时候递归才算走到终结方向。
*/
root.left = insertNode(root.left, node);
/*
* *********************** 重要 ***************************
* 这里插入完成了,然后我们就看是否打破的平衡
* 因为是插入到左子树所以使用 height(root.left) - height(root.right)
* 如果等于2,那当前root 也就是最小不平衡子树。需要将 root节点进行旋转。然后再赋值给root
* 另外,(左子树 - 右子树 = 2) 的情况有两种。
* 所以需要判断 root 的左子树K1,看看 K2插入到 K1的那个地方(root,k1,k2 看文章内解释)
* 这时候如果 (K1 的右孩子 - K1的左孩子 == 1)
* 说明K2插入到K1的右侧了,这时候 tree K1 K2 三者符合 左右关系图,所以调用 left_right_Rotation(root)
*
* else 使用 left_left_Rotation(root);
* 一定要记得无论怎么旋转都要给 root赋值
* *********************** 重要 ***************************
*/
if (height(root.left) - height(root.right) == 2) {
//符合 左子树的右子树导致不平衡
if (height(root.left.right) - height(root.left.left) == 1) {
root = left_right_Rotation(root);
} else {
root = left_left_Rotation(root);
}
}
} else if (cmp > 0) {
/*
* 与上面解释异曲同工
*/
root.right = insertNode(root.right, node);
//右子树的插入导致不平衡
if (height(root.right) - height(root.left) == 2) {
//右子树的左子树导致不平衡
if (height(root.right.left) - height(root.right.right) == 1) {
root = right_left_Rotation(root);
} else {
root = right_right_Rotation(root);
}
}
} else {
//节点相同
}
} else {
//如果root为空,则将node赋值给root
root = node;
}
/*
* 这一步很重要
* 我们说节点插入完成后,高度就需要有个改变,那高度怎么计算,当然是左子树的高和右子树的高,取最大值,然后 + 1
*/
root.height = max(height(root.left), height(root.right)) + 1;
return root;
}
/**
* 外部使用,因为无论如何插入,首先肯定要和 mRoot进行比较,同时mRoot也要随时等待被重新赋值
*
* @param node
*/
public void insert(BaseNode node) {
mRoot = insertNode(mRoot, node);
}
剩下的就是旋转的代码了,其实旋转大家都了解了。我就不解释了。
我把代码放github了
https://github.com/ai2101039/AVLTree