一、AVL树基本概念
1.1定义
如果一颗二叉搜索树的左右子树的高度差的绝对值不超过1(1,0,-1),那么这颗二叉搜索树就叫AVL树。
1.2AVL树的性质
AVL树的左右子树也是一颗AVL树,二叉搜索树是一颗高度平衡的二叉树,它查找值的时间复杂度可以保持在树的高度。
二、AVL树的实现
2.1AVL树节点的定义
static class TreeNode {public int val;public TreeNode left;public TreeNode right;public TreeNode parent;//记录当前节点的父亲节点public int bf;//平衡因子public TreeNode(int val) {this.val = val;}}
注意:平衡因子的值为右子树高度减去左子树的高度。
2.2AVL树的插入
AVL树的插入逻辑与二叉搜索树的插入逻辑相同,都是通过与节点比较大小来确定新节点的插入位置,但是对于AVL树,每插入一个节点都需要更新这个节点的父亲节点和这个节点的祖先节点的平衡因子;
如果平衡因子大于1,就需要根据平衡因子的值来确定具体的旋转方法:
<1>左单旋:新节点插入到较高右子树的右边。
<2>右单旋:新节点插入到较高左子树的左边。
<3>左右双旋:新节点插入到较高左子树的右边。
<4>右左双旋:新节点插入到较高右子树的左边。
!!! 可以看到,AVL树的插入会涉及到树的旋转操作,也就是说,如果大量插入节点或者大量删除节点都会涉及到树的多次旋转,导致时间复杂度过高,因此AVL树只适合用来存储一些不会频繁更新的元素,对于AVL树,主要利用的是它的查找效率。
2.3AVL树插入代码实现
下面是四种旋转方法示意图:
代码实现:
class MyAvlTree {static class TreeNode {public int val;public TreeNode left;public TreeNode right;public TreeNode parent;//记录当前节点的父亲节点public int bf;//平衡因子public TreeNode(int val) {this.val = val;}}public TreeNode root;//根结点//向avl树插入值public boolean insert(int val) {if (root == null) {root = new TreeNode(val);return true;}TreeNode node = new TreeNode(val);TreeNode cur = root;TreeNode prev = null;//记录cur的父亲节点while (cur != null) {if (cur.val < val) {prev = cur;cur = cur.right;} else if (cur.val > val) {prev = cur;cur = cur.left;} else {return false;}}//判断最后一个节点的值的大小if (prev.val < val) {prev.right = node;} else {prev.left = node;}//插入一个节点后从新插入节点的父亲节点开始调节平衡因子node.parent = prev;cur = node;//调节平衡因子while (prev !=null) {//调节到根节点结束//通过判断cur是prev的左树还是右树,决定平衡因子++还是--if (cur == prev.right) {prev.bf++;} else {prev.bf--;}if (prev.bf == 0) {//说明已经平衡了,不需要再向上调整平衡因子break;} else if (prev.bf == 1 || prev.bf == -1) {//继续向上调整,修改平衡因子cur = prev;prev = prev.parent;} else {if (prev.bf == 2) {//说明右树高,需要将右树的节点给一部分给左树if (cur.bf == 1) {//左旋rotateRight(prev);} else {//右左双旋rotateRL(prev);}} else {//平衡因子为-2,说明左树高,将左树的节点给一部分给右树if (cur.bf == -1) {//右旋rotateRight(prev);} else {//左右双旋rotateLR(prev);}}//到这里说明平衡了break ;}}return true;}private void rotateRL(TreeNode prev) {TreeNode subL = prev.left;TreeNode subLR = subL.right;int bf = subLR.bf;rotateRL(prev.left);rotateRL(prev);if (bf == -1) {subLR.bf = 0;prev.bf = 1;subL.bf = 0;} else if(bf == 1){subLR.bf = 0;prev.bf = 0;subL.bf = -1;}}private void rotateLR(TreeNode prev) {TreeNode subL = prev.left;TreeNode subLR = subL.right;int bf = subLR.bf;rotateLeft(prev.left);rotateRight(prev);//调整平衡因子if (bf == -1) {subL.bf = 0;prev.bf = 1;subLR.bf = 0;} else if(bf == 1){subLR.bf = 0;subL.bf = -1;prev.bf = 0;}}//右单旋private void rotateRight(TreeNode prev) {TreeNode subL = prev.left;TreeNode subLR = subL.right;prev.left = subLR;subL.right = prev;prev.parent = subL;//没有subLR节点if (subLR != null) {subLR.parent = prev;}if (prev == root) {//检查prev是否为根结点subL.parent = null;root = subL;} else {TreeNode Pparent = prev.parent;//记录prev的父亲节点if (Pparent.left == prev) {//如果prev是它父亲节点的左节点Pparent.left = subL;subL.parent = Pparent;} else {//如果prev是它父亲节点的右节点Pparent.right = subL;subL.parent = Pparent;}}//修改平衡因子prev.bf = 0;subL.bf = 0;}//左单旋private void rotateLeft(TreeNode prev) {TreeNode subR = prev.right;TreeNode subRL = subR.left;prev.right = subRL;subR.left = prev;prev.parent = subR;if (subRL != null) {subRL.parent = prev;}if (prev == root) {subR.parent = null;root = subR;} else {TreeNode Pparent = prev.parent;if (Pparent.left == prev) {subR.parent = Pparent;Pparent.left = subR;} else {subR.parent = Pparent;Pparent.right = subRL;}}//修改平衡因子prev.bf = 0;subR.bf = 0;}
}
2.4AVL树的验证
如果一个二叉树的节点中没有记录平衡因子,就需要通过递归来计算左右子树高度差,如果不大于1,说明是平衡二叉树,否则不是平衡二叉树。
具体代码如下:
private boolean isBalance(TreeNode root) {if (root == null) return true;int leftH = height(root.left);int rightH = height(root.right);//检查平衡因子if (rightH - leftH != root.bf) {System.out.println(root.val + " 平衡因子异常!");return false;}return Math.abs(leftH - rightH) <= 1 &&isBalance(root.left) && isBalance(root.right);}
2.5AVL树性能分析
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要 维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。