前言
如果让我们在一堆带有标识的数据中,去高效查询一个特定标识的数据,我们有几种方法?通常我们都会知道二分查找法,二分查找法确实是一种比较高效的查找算法,但是也有弊端就是使用二分查找的数据必须要先经过排序而且针对链表,树这种数据结构并不能像数组一样根据坐标去直接访问。
原理
二叉查找树
那么根据上面所说的情况对树型结构的查找就有人提出了下图的形式即子节点和自己的父节点进行比较,比父节点大的放到父节点右边,比父节点小的放到父节点左边这样我们在查找的时候就不用遍历整个树而且可以做到有方向的查找,效率较高。
看了上图大家可能就会有发现,此种方法的查找的效率和树的高度有关,这种结构叫做二叉查找树这种算法确实可以将我们查找效率提高不少但是查找二叉树可能会由于数据的不同查询的时间又有较大的变化,因为查找二叉树会有以下的情况:
如上图我们可以发现如果我们的数据是经过排序的,那么查找树的结构其实就是链表结构,查找的效率也会退化成链表结构的查找效率。
平衡查找二叉树(AVL树)
出于二叉查找树的最坏情况考虑,提出了平衡查找二叉树,特点如下:在平衡查找二叉树中,任何一个根节点的左右子树高度差都不能大于1。
那面我们如何保证平衡查找二叉树的特点呢?这就需要我们在插入节点和删除节点时,做一些节点位置上的平衡和处理来维持这个特性。
维持这个特性的操作我们称之为左旋和右旋操作(我们只会对最小范围的子树进行操作),下面我们会有实际的讲解:
-
左旋: 根节点以右子树为轴进行旋转。
-
右旋: 根节点以左子树为轴进行旋转。
一般来说查找二叉树插入一个新节点后会出现以下四种类型:LL型,LR型,RR型,RL型以下是这几种类型的介绍与保持平衡的方法。 -
LL型
对于插入节点后最小二叉树是LL型我们采用右旋操作:
-
LR型
对于插入节点后最小二叉树是LR型我们采用先左旋再右旋操作:
-
RR型
对于插入节点后最小二叉树是LL型我们采用左旋操作:
-
RL型
对于插入节点后最小二叉树是LR型我们采用先右旋再左旋操作:
实现
AVLTree
import java.util.Comparator;
public class AVLTree<K, V> {
//根节点
private AVLNode<K, V> root;
//平衡二叉树左树大于右树的最大差值
private static final int MAX_AVL_ALLOW_LIMIT = 1;
//平衡二叉树右树大于左树的最大差值
private static final int MIN_AVL_ALLOW_LIMIT = -1;
//Key比较器
private Comparator<K> comparator;
//树当前大小
private int size = 1;
public AVLTree(Comparator<K> comparator) {
this.comparator = comparator;
}
public void add(K key, V value) {
AVLNode<K, V> node = new AVLNode<>(key, value);
//如果root为空,初始化
if (root == null) {
root = node;
return;
}
//插入
insert(root, node);
//平衡二叉树
balance(node);
}
public void remove(K key) {
AVLNode<K, V> node = find(root, key);
if (node == null) return;
//最上层节点删除
if(root == node) {
//左子树为空的情况
if(node.left == null) root = root.right;
//右子树为空的情况
else if(node.right == null) root = root.left;
else {
AVLNode<K,V> tNode = node.left.right;
node.left.setNode(node.right, AVLNode.RIGHT_NODE_INDEX);
node = node.left;
node.setNode(null, AVLNode.PARENT_NODE_INDEX); //父节点职null
//左子树挂到右子树的左子树下面
leftNodeMoveRightNode(node, tNode);
root = node;
}
}
//叶子结点删除
else if(node.left == null && node.right == null) {
node.parent.removeLeaf(node.parent.isChildNode(node));
}
//根节点被删除,优先左节点替换
else {
AVLNode<K,V> pNode = node.parent;
int index = pNode.isChildNode(node);
pNode.removeLeaf(index);
if (node.left != null) {
pNode.setNode(node.left, index);
//如果左子树根节点的右子树不为空
AVLNode<K, V> rNode = node.left.right;
if(rNode != null) {
if(node.right != null) {
leftNodeMoveRightNode(node, rNode);
}
node.left.setNode(node.right, AVLNode.RIGHT_NODE_INDEX); //设置右节点
}
} else {
//直接设置右节点即可
pNode.setNode(node.right, index);
}
}
size--;
//获取root树的最下层叶子结点并进行平衡检验
balance(findLowestLeafNode(root));
}
//左子树挂到右子树的左子树下面
private void leftNodeMoveRightNode(AVLNode<K, V> node, AVLNode<K, V> tNode) {
AVLNode<K, V> rrlNode = node.right;
do {
rrlNode = rrlNode.left;
} while (rrlNode.left