平衡二叉树(AVL)
问题引入:
在二分搜索树(二叉排序树)中如果面对一个有序序列的插入,则会造成二分搜索树退化成为链表。例如对于序列:3,4,5,6,7依次插入二分搜索树时会形成一个单链表。
此时虽然二分搜索树的插入与删除操作没有收到影响,但是搜索操作的效率是非常低,甚至不如链表的检索效率。
概念
平衡二叉树,也叫做平衡二叉搜索树或者AVL树
特点:树中任何一个结点的左右子树的高度相差不超过1
实现方法:AVL(算法),红黑树,替罪羊树,Treap,伸展树
实现平衡二叉树的方法
计算树高
结点的树高是进行平衡判断的基本条件
计算树高只需要计算其左右子树的高度的最大值,利用最大值加一即是当前结点的树高(递归)
//计算当前结点的高度
public int height(Node node){
if (node == null){
return 0;
}
//计算左右子树中高度较高的一边,并且加上当前的层数
return Math.max(node.left == null?0:height(node.left) , node.right == null?0:height(node.right)) + 1;
}
单旋转
单旋转分为左旋转与右旋转,进行左右旋转的条件需要根据当前结点左右子树的高来进行判断
左旋转
目的:降低右子树的高度
条件:当结点的右子树的高度 - 左子树的高度大于1时进行
步骤:
1.创建新结点,值为根结点的值 newNode.value = root.value |
---|
2.新结点的左子树指向根结点的左子树 newNode.left= root.left |
3.新结点的右子树指向根结点的右子树的左子树 newNode.right = newNode.right.left |
4.根结点的值替换为根结点右子树结点的值 root.value = root.right.value |
5.根结点的右子树指向根结点右子树的右子树 root.right = root.right.right |
6.根结点的左子树指向新结点 root.left = newNode |
//左旋转
public void leftRotate(Node node){
//创建新结点
Node newNode = new Node(node.value);
//新结点的左子树指向根结点左子树
newNode.left = node.left;
//新结点的右子树指向根结点右子树的左子树
newNode.right = node.right.left;
//根结点的值替换为根结点右子树结点的值
node.value = node.right.value;
//根结点的右子树指向根结点右子树的右子树
node.right = node.right.right;
//根结点的左子树指向新的结点
node.left = newNode;
}
右旋转
目的:降低左子树的高度
条件:当结点的右子树的高度 - 左子树的高度大于1时进行
步骤:
1.创建新结点,值为根结点的值 newNode.value = root.value |
---|
2.新结点的右子树指向根结点的右子树 newNode.right = root.right |
3.新结点的左子树指向根结点的左子树的右子树 newNode.left= newNode.left.right |
4.根结点的值替换为根结点左子树结点的值 root.value = root.left.value |
5.根结点的左子树指向根结点左子树的左子树 root.left= root.left.left |
6.根结点的右子树指向新结点 root.right= newNode |
//右旋转
public void rightRotate(Node node){
//创建新结点值为根节点的值
Node newNode = new Node(node.value);
//新结点的右子树指向根结点的右子树
newNode.right = node.right;
//新结点的左子树指向根结点左子树的右子树
newNode.left = node.left.right;
//根结点值替换为左子树结点的值
node.value = node.left.value;
//根结点左子树指向左子树的左子树
node.left = node.left.left;
//根结点的右子树指向新结点
node.right = newNode;
}
双旋转
单单凭借单旋转的左右旋转,遇到特殊情况时就无法进行平衡了
例如序列10,11,7,6,8,9在进行右旋转之后
可以发现在进行旋转之后还是存在不平衡的情况
解决办法就是双旋转
右旋转时:
1.进行右旋转时,若其结点的左子树的右子树高度大于左子树的左子树的高度
2.则先对其左子树进行左旋转
3.在对其进行右旋转
//当前结点的左子树的高大于右子树的高时需要进行右旋转,height()方法计算当前结点的高度
if ((height(node.left) - height(node.right) > 1)){
//判断双旋转的条件,当左子树的右子树的高度大于左子树的右子树的高度时
if (height((node.left.right)) > height(node.left.left)){
//先对其左子树进行左旋转
leftRotate(node.left);
}
//右旋转
rightRotate(node);
}
左旋转时:
1.当进行左旋转时,若其右子树的左子树的高度大于其右子树的右子树的高度时
2.则先对其右子树进行右旋转
3.再对原结点进行左旋转
//当前结点的右子树的高大于左子树的高时需要进行左旋转
if ((height(node.right) - height(node.left) > 1)){
//判断双旋转的条件,右子树的左子树的高大于右子树的右子树的高
if (height(node.right.left) > height(node.right.right)){
//先对其右子树进行右旋转
rightRotate(node.right);
}
//再对其进行左选择
leftRotate(node);
}