平衡二叉树
平衡二叉树(Self-balancing binary search tree) 自平衡二叉查找树 又被称为AVL树(有别于AVL算法)
平衡因子(平衡度):结点的平衡因子是结点的左子树的高度减去右子树的高度。(或反之定义)
平衡二叉树: 每个结点的平衡因子都为 1、-1、0 的二叉排序树。或者说每个结点的左右子树的高度最多差1的二叉排序树。
目的: 平衡二叉树的目的是为了减少二叉查找树层次,提高查找速度
常用实现方法: 有AVL、红黑树、替罪羊树、Treap、伸展树等
平衡二叉树
红黑树
让每个节点的左右子树之间的高度差减小。一种平衡二叉树。
- 节点是红色或黑色。
- 根节点是黑色。
- 每个叶节点(NIL节点,空节点)是黑色的。
- 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。
将空节点作为叶子节点,在本博客红黑树的变换中的图例中,未将空节点画出来
红黑树的变换
- 变颜色
- 左旋
- 右旋
左旋
右旋
以上两个 gif 图为转载
旋转和颜色变换规则:
- 所有插入的点默认为红色;
- 变颜色的情况:当前结点的父亲是红色,且它的爷爷的另一个子结点(叔叔结点)也是红色∶
a. 把父节点、叔叔节点设为黑色
b. 把爷爷设为红色
c. 把当前指针指向爷爷节点(继续使用 2、3、4进行判断) - 左旋:
父节点是红色,叔叔是黑色的时候
,且当前节点是右子树。左旋以父结点作为左旋的节点。 - 右旋:
父节点是红色,叔叔是黑色的时候
,且当前节点是左子树。右旋
a. 把父亲变为黑色
b. 把爷爷变为红色
c. 以爷爷旋转
示例:
代码
以下代码主要体现其思想,具体有些情况未考虑!!! 仅作参考!!!
public class Main {
private final int A = 1;
private final int B = 0;
private Node root = null;
class Node{
int data;
int color;
Node left;
Node right;
Node pa;
public Node(int data)
{
this.data = data;
}
}
// 插入节点
public void insert(Node root, int data)
{
if(root.data < data)
{
if(root.right == null)
root.right = new Node(data);
else
insert(root.right, data);
}else
{
if(root.left == null)
root.left = new Node(data);
else
insert(root.left, data);
}
}
// 需要修改指向的点
// 1. E节点的父亲节点的孩子指针的指向(需要考虑是否是root)
// 2. S节点
// 3. E节点
// 4. S和E之间的节点
public void leftRotate(Node node)
{ // node 节点是 E 节点
if(node.pa == null) // 当前节点为根节点
{
Node E = root;
Node S = E.right; // S 节点为空怎么办?
// 移动S的左子树
E.right = S.left;
S.left.pa = E; // S的左子树为空怎么办?
// 修改E的pa指针
E.pa = S;
// 修改S的pa指针
S.pa = null;
}else{
Node E = node;
Node S = E.right; // 为了和动图对应
if(node == node.pa.left) // 父节点的指针指向 当前节点的右子树
node.pa.left = E.right;
else
node.pa.right = E.right;
E.right = S.left; // 移动S的左子树
S.left.pa = E; // S的父亲指针指向E
S.pa = E.pa; // 更改S的父亲指针
E.pa = S; // 更改E的父亲指针
S.left = E; // 更改S的左子树指针
}
}
public void rightRotate(Node node) {
// node 节点是 S 节点
if (node.pa == null) // 当前节点为根节点
{
Node S = root;
Node E = S.left; // E 节点为空怎么办?
// 移动 E 的右子树
S.left = E.right;
E.right.pa = S; // E的右子树为空怎么办?
// 修改 S 的pa指针
S.pa = E;
// 修改 E 的pa指针
E.pa = null;
} else {
Node S = node;
Node E = S.right; // 为了和动图对应
if (node == node.pa.left) // 父节点的指针指向 当前节点的右子树
node.pa.left = S.left;
else
node.pa.right = S.left;
S.left = E.right; // 移动E的右子树
E.right.pa = S; // E的父亲指针指向S
E.pa = S.pa; // 更改E的父亲指针
S.pa = E; // 更改S的父亲指针
E.right = S; // 更改E的右子树指针
}
}
}
leetCode中有个童鞋写的 基于平衡二叉树的不可重复Set
AVL树 & 红黑树的区别
- 红黑树并不追求完全平衡——它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能。
- 红黑树能够以O(log2 n) 的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内解决。当然,还有一些更好的,但实现起来更复杂的数据结构 能够做到一步旋转之内达到平衡,但红黑树能够给我们一个比较“便宜”的解决方案。
- 红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高。当然,红黑树并不适应所有应用树的领域。
- 如果数据基本上是静态的,那么让他们待在他们能够插入,并且不影响平衡的地方会具有更好的性能。
- 如果数据完全是静态的,例如,做一个哈希表,性能可能会更好一些。在实际的系统中,例如,需要使用动态规则的防火墙系统,使用红黑树而不是散列表被实践证明具有更好的伸缩性,典型的用途是实现关联数组。
- AVL树是最先发明的自平衡二叉查 找树。在AVL树中任何节点的两个儿子子树的高度最大差别为一,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。