一.先看一个案例
这是二叉排序树会存在的一个问题,先看案例:
给定一个数列为{1,2,3,4,5,6},将这个数列转换为二叉排序树
根据二叉排序树的性质可以得到这样的二叉排序树,这样的树有这么几个问题:
- 左子树全部为空,从形式上看更像是一个链表
- 对于二叉树的插入速度没有影响
- 查询速度明显降低(需要依次比较),不能发挥出二叉排序树的特点
由于这样的情况,便可以引出平衡二叉树,平衡二叉树就可以完美的解决这个问题
二.基本介绍
- 平衡二叉树也叫二叉搜索树又被称为AVL树,可以保证拥有较高的查询效率
- AVL树具有以下特点,它是一颗空树,或它的左右两个子树的高度差(平衡因子)的绝对值不超过1,并且左右两个子树都是一颗平衡二叉树。
1.举例说明
二.构建的方式
那么如何构建一个平衡二叉树,我们可以理解为,将原有不平衡的二叉树,进行一次旋转就可以构成一个二叉树,如图
对这个二叉排序树进行旋转:
经过这样的旋转之后,就可以得到一个平衡二叉树
经过这个旋转我们可以看到:
- 原有的根节点经过旋转到了左子树,而原来右子树的第一个结点,成了根节点
- 原来右子树的左子节点成了左子树的右子节点
这个过程非常像进行了一次旋转,所以称这次旋转为左旋转。这种旋转既保留了二叉排序树的特点又降低了左右两颗子树的高度差,这就是构建一颗平衡二叉树的方式
1.计算左右两子树高度
对左右两子树进行递归就可以得出
/**
*
* @return 返回左子树的高度
*/
public int leftHeight(){
if(left == null){
return 0;
}
return left.height();
}
/**
*
* @return 返回右子树的高度
*/
public int rightHeight(){
if(right == null){
return 0;
}
return right.height();
}
/**
*
* @return 返回以当前结点为根节点的树的高度
*/
public int height(){
return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
}
2.左旋转实现方式
- 创建一个新的结点newNode,值等于当前根节点的值
- 把新的结点的左子树设置为当前根节点的左子树。newNode.left = this.left
- 把新节点右子树设置为当前节点的右子树的左子树,newNode.right = this.right.left
- 把当前结点的值换为右子节点的值,this.value = this.right.value
- 把当前节点的右子树设置为右子树的右子树(相当于扔掉了当前结点的右子节点,此时当前结点的值已经是右子节点的值了),this.right = this.right.right
- 把当前节点的左子树设置为新的结点,this.left = newNode
这样就实现了构建平衡二叉树的左旋转,解决来了右子树高度高于左子树高度的问题
代码:
/**
* 左旋转方法
*/
private void leftRotate(){
//创建新的结点
Node newNode = new Node(value);
//把新的结点的左子树设置成当前结点的左子树
newNode.left = left;
//把新的结点的根节点的右子节点的左子节点设置成右子树
newNode.right = right.left;
//把根节点的值设为根节点的右子节点
this.value = this.right.value;
//将根节点的值指向右子节点的右子节点
this.right = this.right.right;
//把根节点的左子树设置为新节点
this.left = newNode;
}
3.右旋转实现方式
既然左旋转能解决右子树高度大于左子树高度的问题,那么必然也会有右旋转解决左子树高度过高的办法。右旋转和左旋转的方式相似,只是方向的改变
- 首先定义一个新节点newNode,值等于当前根结点的值
- 然后将当前根节点的右子节点赋给新的结点,newNode.right = this.right
- 再将当前结点的左子节点的右子节点赋给新节点的newNode.left = this.left.right
- 将当前结点的值设为左子节点的值,this.value = this.left.value
- 将当前结点的左子节点指向左子节点的左子节点,this.left = this.left.left
- 将当前结点的右子节点指向新的结点,this.right = newNode
代码:
/**
* 右旋转方法
*/
private void rightRotate(){
//创建新的结点
Node newNode = new Node(this.value);
//将新节点的rights设为跟的right
newNode.right = this.right;
//将新节点的left设为根节点的left的right
newNode.left = this.left.right;
//将根节点的值换成根节点的left的值
this.value = this.left.value;
//将根节点的left指向left的left
this.left = this.left.left;
//将根节点的right指向newNode
this.right = newNode;
}
4.存在的问题
在旋转过程中发现了一个问题,每次在旋转时给新节点挂上的根节点的右子节点的左子节点(左子节点的右子节点)如果还有子节点,就会出现旋转后还没有平衡的问题,这时我们需要对该结点的左子节点(右子节点)的子树进行一次左旋转(右旋转)
判断过程:
//当添加结点后,(右子树的高度 - 左子树的高度) > 1,左旋转
if(rightHeight() - leftHeight() > 1){
if(right != null && right.leftHeight() > right.rightHeight()){
//如果当前的右子树的左子树高度大于右子树高度
//先右旋转
this.right.rightHeight();
//再左旋转
this.leftRotate();
}else {
//左旋转
leftRotate();
}
return;
}
//当添加结点后, (左子树的高度 - 右子树的高度)> 1,有旋转
if(leftHeight() - rightHeight() > 1){
//如果他的左子树的右子树的高度大于它的左子树的高度
if(left != null && left.rightHeight() > left.leftHeight()){
//先对当前结点的左节点进行左旋转
this.left.leftRotate();
//右旋转
rightRotate();
}else {
rightHeight();
}
return;
}
三.代码实现
由于代码量过大请访问我的github,代码在树分支中(类名:AVLTreeDemo)
访问GitHub