AVL树
AVL树也叫二叉平衡搜索树。
提到AVL树又不得不提起平衡树和搜索树,
本文中将通过AVL树的定义特征,以及AVL的查找和插入操作来介绍AVL树。
搜索树的定义:
对于一棵二叉树,要想使之成为搜索树需满足一下条件:
1.任取一个节点,左孩子.key<自身.key<右孩子.key。
2.中序遍历是有序的。
注意,此时key值比较不仅仅是针对于左孩子,而是左孩子及左孩子中所有的子节点,
比如:
就不是一棵搜索树,因为左子树中6这个节点的值不小于根节点的值,所以不能称之为搜索树。
平衡树的定义:
AVL中对平衡(Balance)的定义:
如果:
在一个树中任取一个结点,如果,这个结点的左子树的高度和右子树的高度的高度差的绝对值小于等于1,满足这样的条件的树我们称之为平衡树。
例如:
就是一棵平衡树,所有结点的左右子树高度差均不超过1.
而:
就不是一棵平衡二叉树,因为对于根结点来说,左子树的高度达到了4,但是右子树的高度只有2,因而不能称之为平衡树。
平衡因子(Balance Factor | BF)
在AVL树中,还会引入一个叫做平衡因子的概念。
对于一个node来说,所谓平衡因子:
node 的BF = H(node.left) - H(node.right);
从上面的式子中来看,很容易得出平衡因子的概念:
一个结点的平衡因子为左子树和右子树的高度差,
所以,对于一个平衡二叉树来说,他的任意一个结点的平衡因子的取值只可能是:-1,0,1;
AVL树的查找
AVL树的查找规则
AVL树的查找类似于搜索树的查找。
我们知道,对于一个二叉搜索树来说,对于任意一个结点,所有左孩子的值都比这个结点小,所有右孩子的值都比结点大,所以:
1.在二叉搜索树中进行查找的时候,只需要每次都跟根结点进行比较,
2.如果找的值<根节点,则向下遍历左子树,重复该步骤,
3.如果要找的值>根节点的值,则向下遍历右子树。
4.重复上面的步骤直到找到对应的结点。
如下图: 现在需要查找6这个结点
1.定义一个current = 当前的根节点,先将current.key与6进行判断,
2.可以得到6>current.key,所以继续向下遍历右子树。current = current.right,
3.再次进行比较6<current.key,所以向下遍历左子树,current = current.left,
4.再次进行比较发现6 = current.key,return返回。
下面是二叉搜索过程的代码实现:
package high_level.AVLTree;
/**
* @Name: BSTree
* @Description: 普通搜索树的定义
* @Author: panlai
* @Date: 2021/8/14 16:43
*/
class BSTNode {
public long key;
public BSTNode left;
public BSTNode right;
}
public class BSTree {
public BSTNode root;
//二叉搜索方法的实现
public BSTNode search(long key){
BSTNode cur = root;
while (cur!= null){
if (key == cur.key){
return cur;
}
else if (key<cur.key){
cur = cur.left;
}else {
cur = cur.right;
}
}
//走到这一步,相当于没找到。
return null;
}
}
AVL树的插入
AVL树的插入规则
首先在进行插入之前,也要进行查找,找到要插入的位置,而且,找的这个位置一定是发生在叶子节点上的。所以插入操作是基于查找操作的。
代码如下:
//如果插入失败(key重复),以异常方式体现,
public void insert(int key){
BSTNode node = new BSTNode();
node.key = key;
if (root == null){
root = node;
return;
}
BSTNode cur = root;
BSTNode parent = null;
while (cur!= null){
if (key == cur.key){
throw new RuntimeException("插入的key值重复了!!");
}else if (key<cur.key){
parent = cur;
cur = cur.left;
}else {
parent = cur.right;
cur = cur.right;
}
}
//不会出现等于的情况了
if (key<parent.key){
parent.left = node;
}else {
parent.right = node;
}
}
调整平衡因子
随着插入的完成,会导致AVL平衡因子的变化,,所以还需要进行平衡因子的调节。
当插入的结点位于该位置父亲节点的左孩子时,平衡因子bf++,为右孩子时,平衡因子–;
代码如下:
private void avlAdjust(AVLTreeNode parent, AVLTreeNode node){
//parent != null && node != null
//进行平衡因子的调整
if (node == parent.left){
parent.bf++;
}else {
parent.bf--;
}
}
树的旋转(rotate)
首先,一个树要想满足搜索树必须要满足下面的条件:(三角形为子树,圆形为结点)
以a为结点右旋:
旋转的目的是为了保证原有搜索树的特征不变,但是会导致树的高度改变。
关于左旋与右旋可以参考这篇文章:左旋与右旋
由于在插入结点之后有可能会导致平衡树失衡,失衡的情况分为以下四种:
针对不同的失衡的情况,也有不同的解决方案:
LL:对parent做右旋,插入结束。
LR:先对node结点做左旋,在对parent做右旋,插入结束。
RR:对parent做左旋,插入结束。
RL: 先对node做右旋,在对parent做左旋,插入结束。