一、 二叉平衡树
概念
二叉搜索树有称 二叉排序树,它也可以是一个空树。
- 如果它的左子树不为空,则左子树上所有结点的值都小于根结点的值
- 如果他的右子树不为空,则右子树上所有结点的值都大于根结点的值
- 它的左右子树也分别是二叉搜索树
由图可以看出:二叉搜索树中最左侧的节点是树中最小的节点,最右侧节点一定是树中最大的节点
采用中序遍历遍历二叉搜索树,可以得到一个有序的序列:
1 3 4 5 6 7 8 9 10
二叉搜索树的查找
若根结点不为空:
如果根结点的值==target值 返回true
如果根结点的值 > target值 在其左子树查找
如果根结点的值 < target值 在其右子树查找
否则 返回false
二叉树查询效率【性能分析】
最优情况:二叉搜索树为完全二叉树,其平均比较次数
最差情况:二叉搜索树退化成单支树,其平均比较次数
二、 AVL树【高度平衡的二叉搜索树】
概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。
当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过
1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
下面说一下二叉搜索树的特点:
- 它的左子树右子树都是AVL树。
- 左右子树高度之差的绝对值不超过 1
如果一颗二叉搜索树是高度平衡的,他就是AVL树。它有n个结点,其高度可保持在 O(log2N),搜索时间复杂度O( log2N)。
AVL树结点的定义
为了AVL树实现简单,AVL树节点在定义时维护一个平衡因子,具体节点定义如下:
static class TreeNode {
public int val;
public int bf; //平衡因子
public TreeNode left;
public TreeNode right;
public TreeNode parent;
public TreeNode(int val) {
this.val = val;
}
}
AVL树的插入
public TreeNode root;
public boolean insert(int val) {
TreeNode node = new TreeNode(val);
if (root == null) {
root = node;
return true;
}
TreeNode parent = null;
TreeNode cur = root;
while (cur != null) {
if (cur.val < val) {
parent = cur;
cur = cur.right;
} else if (cur.val == val) {
return false;
} else {
parent = cur;
cur = cur.left;
}
}
//cur == null
if (parent.val < val) {
parent.right = node;
} else {
parent.left = node;
}
//以上是将数据插入到AVL树中 【和二叉搜索树一样】
//平衡因子 = 右子树高度 - 左子树高度
node.parent = parent;
//插入之后,根据平衡因子来进行树的调整
cur = node;
//调节平衡因子
while (parent!=null) {
//先看cur是parent的左还是右 决定平衡因子是++ 还是 --
if (cur == parent.right) {
parent.bf++;
} else {
parent.bf--;
}
//检查当前的平衡因子 是不是绝对值 1 0 -1
if (parent.bf == 0) {
//说明平衡
break;
} else if (parent.bf == 1 || parent.bf == -1) {
//1 和 -1 代表当前分支树平衡 不代表整个树平衡
// 继续向上判断 修改平衡因子
cur = parent;
parent = cur.parent;
} else {
if (parent.bf == 2) {
// parent.bf == 2 右树高 需要降低右树的高度
if (cur.bf == 1) {
rotateLeft(parent);
} else {
// parent.bf == -1
//右左双旋
}
} else {
// parent.bf == -2 左树高 需要降低左树的高度
if (cur.bf == -1) {
//右旋
rotateRight(parent);
} else {
// parent.bf == 1
//左右双旋
rotateLR(parent);
}
}
break;
}
}
return false;
}
AVL树的旋转
右单旋
左单旋
左右双旋
右左双旋
旋转代码:
//右单旋
private void rotateRight(TreeNode parent) {
TreeNode subL = parent.left;
TreeNode subLR = subL.right;
parent.left = subLR;
if (subLR != null) {
subLR.parent = parent;
}
subL.right = parent;
//记录下当前parent的父亲
TreeNode parParent = parent.parent;
parent.parent = subL;
if (parent == root) {
//独立的子树
root = subL;
root.parent = null;//subL.parent = null;
} else {
//不是独立的子树,是其他树的左树或者右树
if (parParent.left == parent) {
// 2. 新节点插入较高右子树的右侧---左单旋
// 左单旋的实现,学生们可以参考右单旋的实现。
parParent.left = subL;
} else {
parParent.right = subL;
}
subL.parent = parParent;
}
subL.bf = 0;
parent.bf = 0;
}
// 左单旋
private void rotateLeft(TreeNode parent) {
//subR为parent的右树
TreeNode subR = parent.right;
//subRL为subR的左树
TreeNode subRL = subR.left;
//1、第一个修改的指向
parent.right = subRL;
//2、第2个修改的指向有可能subRL这棵树是空树
if (subRL != null) {
subRL.parent = parent;
}
//3、第2个修改的指向
subR.left = parent;
//记录下来 之前的parent的父亲
TreeNode parentParent = parent.parent;
//4、第4个修改的指向
parent.parent = subR;
//5、有可能当前父亲节点,不是根
if (parent == root) {
root = subR;
root.parent = null;
} else {
if (parent == parentParent.left) {
parentParent.left = subR;
subR.parent = parentParent;
} else {
parentParent.right = subR;
subR.parent = parentParent;
}
}
subR.bf = parent.bf = 0;
}
//左右双旋
private void rotateLR(TreeNode parent) {
TreeNode subL = parent.left;
TreeNode subLR = subL.right;
int bf = subLR.bf;
rotateLeft(parent.left);
rotateRight(parent);
if(bf == -1) {
subL.bf = 0;
parent.bf = 1;
subLR.bf = 0;
}else if(bf == 1) {
subL.bf = -1;
parent.bf = 0;
subLR.bf = 0;
}
}
//右左双旋
private void rotateRL(TreeNode parent) {
TreeNode subR = parent.right;
TreeNode subRL = subR.left;
int bf = subRL.bf;
rotateRight(parent.right);
rotateLeft(parent);
if (bf == 1){
parent.bf = -1;
subR.bf = 0;
subRL.bf = 0;
}else if(bf == -1){
parent.bf = 0;
subR.bf = 0;
subRL.bf = 1;
}
}
AVL树的验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
- 验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
比特就业课 - 验证其为平衡树
- 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
- 节点的平衡因子是否计算正确
验证当前树是不是 AVL树,如果是,该怎么验证? 【高度平衡的二叉搜索树】
//中序遍历的结果是有序的 就能说明当前树 一定是AVL树吗? 不一定的
public void inorder(TreeNode root) {
if (root == null) {
return;
}
inorder(root.left);
System.out.println(root.val + " ");
inorder(root.right);
}
private int height(TreeNode root) {
if (root == null) {
return 0;
}
int leftH = height(root.left);
int rigthH = height(root.right);
return leftH>rigthH?leftH+1:rigthH+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);
}
测试:
AVL树的删除
删除步骤:
- 找到需要删除的结点
- 按照搜索二叉树的删除规则删除结点
- 更新平衡因子,如果出现不平衡,进行旋转 – 单旋/双旋
AVL树的查找效率【性能分析】
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即
。
但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。