在 算法系列(八)数据结构之二叉查找树 中,介绍了二叉查找树的定义和基本操作。大部分操作的平均时间复杂度为O(logN),但是如果预先输入的数据有序,那么一连串的insert操作花费的时间就会很长,时间复杂度为O(N),一种解决方法是增加平衡附加条件,使得任何节点深度都不会过深。AVL树是最先发明的自平衡二叉查找树。
PS:写avl的时候突然发现之前的一个问题,将树的节点暴露了出去,这样不能保证平衡条件不被改变。github上的代码已经做了修改。外部不能直接访问TreeNode
概述:
在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下都是O(log n)。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文 “An algorithm for the organization of information” 中发表它。
定义和特性:
一棵AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树。
高度为 h 的 AVL 树,节点数 N 最多2^h − 1; 最少N(h)=N(h− 1) +N(h− 2) + 1。
最少节点数
N(0) = 0 (表示 AVL Tree 高度为0的节点总数)
N(1) = 1 (表示 AVL Tree 高度为1的节点总数)
N(2) = 2 (表示 AVL Tree 高度为2的节点总数)
插入操作
简单插入一个节点,会破坏平衡二叉树的特性,恢复平衡性,需要做旋转操作。
插入操作之后,只有那些从插入点到根节点路径上的节点平衡关系会被改变,当我们沿着这条路径上行道根并更新平衡信息时,可以发现一个节点,它的新平衡破坏了AVL条件。我们把必须重新平衡的节点叫做α,对于任意节点,最多有两个儿子,出现高度不平衡时高度差2,这种不平衡出现在下面四种情况:
1对α的左儿子的左子树进行一次插入
2对α的左儿子的右子树进行一次插入
3对α的右儿子的左子树进行一次插入
4对α的右儿子的右子树进行一次插入
对于1,4可以使用单旋转解决,2,3可以使用双旋转解决。
单旋转
图解:
分析:
抽象的形容:把树形象的看成柔软灵活的,抓住节点K1,使劲摇动它,使K1称为新的根。根据二叉查找树的性质,
K2>K1,K2>Y,于是K2变为K1的右子树,Y变为K2的左子树。
具体示例
以上是对左儿子的操作,对右儿子的变化类似。
双旋转
图解:
分析:
上图是左右双旋转,需要两次操作。
第一次对以k1为根节点的树进行单旋转。
第二次是以k3为根节点的树进行单旋转。
即,双旋转由两次单旋转构成
代码实现
package com.algorithm.tree;
import java.util.Comparator;
/**
* AVL树
*
* @author chao
*
* @param <T>
*/
public class AVLTree<T> extends BinarySeachTree<T> {
public AVLTree(T root) {
super(root);
}
public AVLTree(T root, Comparator<T> comparator) {
super(root, comparator);
}
@Override
protected BinaryTreeNode<T> insert(BinaryTreeNode<T> cur, BinaryTreeNode<T> x) {
if (cur == null) {
return x;
}
int compareresult = compare(cur, x);
if (compareresult < 0) {
cur.right = insert(cur.right, x);
if (getHeight(cur.right) - getHeight(cur.left) == 2) {
if (compare(x, cur.right) > 0) {
cur = roateWithRightChild(cur);
} else {
cur = doubleWithRightChild(cur);
}
}
} else if (compareresult > 0) {
cur.left = insert(cur.left, x);
if (getHeight(cur.left) - getHeight(cur.right) == 2) {
if (compare(x, cur.left) < 0) {
cur = roateWithLeftChild(cur);
} else {
cur = doubleWithLeftChild(cur);
}
}
} else {
cur.hintcount++;
}
return cur;
}
/**
* 左子树单旋转
*
* @param node
* @return
*/
protected BinaryTreeNode<T> roateWithLeftChild(BinaryTreeNode<T> k2) {
BinaryTreeNode<T> k1 = k2.left;
k2.left = k1.right;
k1.right = k2;
return k1;
}
/**
* 右子树单旋转
*
* @param node
* @return
*/
protected BinaryTreeNode<T> roateWithRightChild(BinaryTreeNode<T> k2) {
BinaryTreeNode<T> k1 = k2.right;
k2.right = k1.left;
k1.left = k2;
return k1;
}
/**
* 左子树双旋转
*
* @param node
* @return
*/
protected BinaryTreeNode<T> doubleWithLeftChild(BinaryTreeNode<T> k3) {
k3.left = roateWithRightChild(k3.left);
return roateWithLeftChild(k3);
}
/**
* 右子树双旋转
*
* @param node
* @return
*/
protected BinaryTreeNode<T> doubleWithRightChild(BinaryTreeNode<T> k3) {
k3.right = roateWithLeftChild(k3.right);
return roateWithRightChild(k3);
}
}
删除操作也会破坏平衡,这里直接使用懒删除
@Override
public void remove(T x) {
BinaryTreeNode<T> node = findNode(root, x);
if (node == null || node.hintcount < 0) {
return;
} else {
node.hintcount--;
}
}
代码实现可以看github,地址https://github.com/robertjc/simplealgorithm
github代码也在不断完善中,有些地方可能有问题,还请多指教
欢迎扫描二维码,关注公众账号