1、概念:
  AVL树本质上还是一个二叉搜索树,不过比二叉搜索树多了一个平衡条件:每个节点的左右子树的高度差不大于1。
二叉树的应用是为了弥补链表的查询效率问题,但是极端情况下,二叉搜索树会无限接近于链表,这种时候就无法体现二叉搜索树在查询时的高效率,而最初出现的解决方式就是AVL树。如下图:

2、旋转
  说到AVL树就不得不提到树的旋转,旋转是AVL维持平衡的方式,主要有以下四种类型。
  2.1、左左旋转
    如图2-1所示,此时A节点的左树与右树的高度差为2,不符合AVL的定义,此时以B节点为轴心,AB间连线为转轴,将A节点旋转至B节点下方,由B节点的父节点变成子节点。实现所有节点的左右子树高
  度差小于等于1。如图2-2。
    

  2.2、右右旋转

    右右旋转与左左旋转类似,但是动作相反,如图2-3,2-4所示,以B节点为轴心,BC间连线为轴,将C节点旋转至B下方,成为B的子节点。实现所有节点的左右子树高度差小于等于1。  

    2.3、左右旋转
      左右旋转稍复杂一点,需要旋转两次,如果用左左旋转的方式直接旋转图2-5中的树,会变成图2-8的样子,此时B节点的左右子树高度差依然没有平衡,所以要先对2-5的树做一步处理,就是以BC为轴,
    将C节点旋转至B节点的位置,如图2-6所示,此时就将树转换成了左左旋转的场景,之后使用左左旋转即可完成平衡。

 

    2.4、右左旋转
      右左旋转与左右旋转类似,也是旋转两次。如下图,具体细节请看下面的代码详解。

3、代码实现
  3.1、新增
    二叉树的插入不在赘述,这里只分析插入时的平衡方法。如果新增节点没有兄弟节点时会引起树的高度变化,此时需要对上层节点的平衡值进行修改,如果出现了不平衡树,则需要调用平衡方法,代码如下:
private void balance(Node node){
Node parent = node.getParent();
Node node_middle = node;
Node node_prev = node; Boolean avl = true;
do{
if(node_middle == parent.getLeft() && (-1 <= parent.getAVL()-1 && parent.getAVL()-1 <= 1)){
//node_middle为parent的左树,此时parent左树高度+1不会造成不平衡。
parent.subAVL();
node_prev = node_middle;
node_middle = parent;
//由于上面对parent的平衡值进行了修改,如果修改后的平衡值为0,说明此时parent节点的高度没有改变,之前较短的左树高度+1,变为与右树高度相同。
if(parent != null && parent.getAVL() == 0)
parent = null;
else
parent = parent.getParent();
}else if(node_middle == parent.getRight() && (-1 <= parent.getAVL()+1 && parent.getAVL()+1 <= 1)){
//node_middle为parent的右树,此时parent右树高度+1不会造成不平衡。
parent.addAVL();
node_prev = node_middle;
node_middle = parent;
//由于上面对parent的平衡值进行了修改,如果修改后的平衡值为0,说明此时parent节点的高度没有改变,之前较短的右树高度+1,变为与左树高度相同。
if(parent != null && parent.getAVL() == 0)
parent = null;
else
parent = parent.getParent();
}else{//出现最小不平衡节点,新增时不需要考虑更高节点,所以直接中断循环,调用平衡方法
avl = false;
}
}while(parent != null && avl); if(parent == null){
return;
}
//选择相应的旋转方式
chooseCalculation(parent, node_middle, node_prev);
}

  3.2、删除

    删除较新增复杂一些,主要是因为存在一次旋转无法达到平衡效果的情况,而且删除本身也分为三种情况,分别是:
    3.2.1、删除叶子节点
      由于叶子节点没有子树,不涉及替换的问题,所以直接删除即可,如果删除节点没有兄弟节点会引起高度变化,此时依次对父级节点的平衡值做对应修改,如果出现不平衡树则要进行旋转。
    3.2.2、删除节点存在一个子节点
      此时将子节点上移,替换删除节点的位置,这个操作势必会造成所在子树的高度变化,所以需要依次对父级节点的平衡值做对应修该,如果出现不平衡树进行旋转操作。
    3.2.3、删除节点存在两个子节点
      这种情况比较复杂,由于存在两个子节点,所以不能简单的将其中一个子节点提高一级,因此需要在节点的子树中搜索一个合适的节点进行替换,之前自己构思的时候选择的是搜寻一个最长子树的最
      接近删除节点的值的叶子节点,具体实现的时候发现需要考虑的场景太多,并不适合,参考算法导论上的思路后改成了搜寻左树的最大节点,此时只需考虑左树的情况即可。实现方法如下:
public void deleteNode(int item){

        Node node = get(item);
if(node == null)
return;
Node parent = node.getParent();
if(!node.hasChild()){//叶子节点
if(parent == null){//删除最后节点
root = null;
return;
}
if(node.hasBrother()){//node有兄弟节点时,需要判断是否需要调用平衡方法
if(node == parent.getLeft())
isBalance(node, 1);
else
isBalance(node, -1);
parent.deleteChildNode(node);
}else{//node没有兄弟节点时,高度减一,需要进行平衡
deleteAvl(node);
parent.deleteChildNode(node);
}
}else if(node.getLeft() != null && node.getRight() == null){//有一个子节点时,将子节点上移一位,然后进行平衡即可
if(parent == null){//删除的是根节点
root = node;
return;
}
if(node == parent.getLeft()){
parent.setLeft(node.getLeft());
}else{
parent.setRight(node.getLeft());
}
node.getLeft().setParent(parent);
deleteAvl(node.getLeft());
}else if(node.getLeft() == null && node.getRight() != null){//有一个子节点时,将子节点上移一位,然后进行平衡即可
if(parent == null){//删除的是根节点
root = node;
return;
}
if(node == parent.getRight()){
parent.setRight(node.getRight());
}else{
parent.setLeft(node.getRight());
}
node.getRight().setParent(parent);
deleteAvl(node.getRight());
}
else{//有两个子节点时,先在节点左树寻找最大节点last,然后删除last,最后将被删除节点的value替换为last的value
Node last = findLastNode(node);
int tmp = last.getValue();
deleteNode(last.getValue());
node.setValue(tmp);
}
node = null;//GC
}
  3.3、旋转
    3.3.1、左左旋转  

private void LeftLeftRotate(Node node){

        Node parent = node.getParent();

        if(parent.getParent() != null && parent == parent.getParent().getLeft()){
node.setParent(parent.getParent());
parent.getParent().setLeft(node);
}else if(parent.getParent() != null && parent == parent.getParent().getRight()){
node.setParent(parent.getParent());
parent.getParent().setRight(node);
}else{
root = node;
node.setParent(null);
}
parent.setParent(node);
parent.setLeft(node.getRight());
if(node.getRight() != null)
node.getRight().setParent(parent);
node.setRight(parent); if(node.getAVL() == -1){//只有左节点时,parent转换后没有子节点
parent.setAVL(0);
node.setAVL(0);
}else if(node.getAVL() == 0){//node有两个子节点,转换后parent有一个左节点
parent.setAVL(-1);
node.setAVL(1);
}//node.getAVL()为1时会调用左右旋转
}
    3.3.2、右右旋转 
private void RightRightRotate(Node node){

        Node parent = node.getParent();

        if(parent.getParent() != null && parent == parent.getParent().getLeft()){
node.setParent(parent.getParent());
parent.getParent().setLeft(node);
}else if(parent.getParent() != null && parent == parent.getParent().getRight()){
node.setParent(parent.getParent());
parent.getParent().setRight(node);
}else{
root = node;
node.setParent(null);
}
parent.setParent(node);
parent.setRight(node.getLeft());
if(node.getLeft() != null)
node.getLeft().setParent(parent);
node.setLeft(parent); if(node.getAVL() == 1){
node.setAVL(0);
parent.setAVL(0);
}else if(node.getAVL() == 0){//当node有两个节点时,转换后层数不会更改,左树比右树高1层,parent的右树比左树高一层
parent.setAVL(1);
node.setAVL(-1);
}
}
    3.3.3、左右旋转
private void LeftRightRotate(Node node){

        Node parent = node.getParent();
Node child = node.getRight(); //左右旋转时node的avl必为1,所以只需考虑child的avl
if(!child.hasChild()){
node.setAVL(0);
parent.setAVL(0);
}else if(child.getAVL() == -1){
node.setAVL(0);
parent.setAVL(1);
}else if(child.getAVL() == 1){
node.setAVL(-1);
parent.setAVL(0);
}else if(child.getAVL() == 0){
node.setAVL(0);
parent.setAVL(0);
}
child.setAVL(0); //第一次交换
parent.setLeft(child);
node.setParent(child);
node.setRight(child.getLeft());
if(child.getLeft() != null)
child.getLeft().setParent(node);
child.setLeft(node);
child.setParent(parent); //第二次交换
if(parent.getParent() != null && parent == parent.getParent().getLeft()){
child.setParent(parent.getParent());
parent.getParent().setLeft(child);
}else if(parent.getParent() != null && parent == parent.getParent().getRight()){
child.setParent(parent.getParent());
parent.getParent().setRight(child);
}else{
root = child;
child.setParent(null);
}
parent.setParent(child);
parent.setLeft(child.getRight());
if(child.getRight() != null)
child.getRight().setParent(parent);
child.setRight(parent);
}

    3.3.4、右左旋转

private void RightLeftRotate(Node node){

        Node parent = node.getParent();
Node child = node.getLeft(); if(!child.hasChild()){
node.setAVL(0);
parent.setAVL(0);
}else if(child.getAVL() == -1){
node.setAVL(1);
parent.setAVL(0);
}else if(child.getAVL() == 1){
node.setAVL(0);
parent.setAVL(-1);
}else if(child.getAVL() == 0){
parent.setAVL(0);
node.setAVL(0);
}
child.setAVL(0); //第一次交换
parent.setRight(child);
node.setParent(child);
node.setLeft(child.getRight());
if(child.getRight() != null)
child.getRight().setParent(node);
child.setRight(node);
child.setParent(parent); //第二次交换
if(parent.getParent() != null && parent == parent.getParent().getLeft()){
child.setParent(parent.getParent());
parent.getParent().setLeft(child);
}else if(parent.getParent() != null && parent == parent.getParent().getRight()){
child.setParent(parent.getParent());
parent.getParent().setRight(child);
}else{
root = child;
child.setParent(null);
}
parent.setParent(child);
parent.setRight(child.getLeft());
if(child.getLeft() != null)
child.getLeft().setParent(parent);
child.setLeft(parent);
}

完整代码github地址:https://github.com/ziyuanjg/AVLTree

AVL树的平衡算法(JAVA实现)的更多相关文章

  1. AVL树的JAVA实现及AVL树的旋转算法

    1,AVL树又称平衡二叉树,它首先是一颗二叉查找树,但在二叉查找树中,某个结点的左右子树高度之差的绝对值可能会超过1,称之为不平衡.而在平衡二叉树中,任何结点的左右子树高度之差的绝对值会小于等于 1. ...

  2. AVL树(平衡二叉查找树)

    首先要说AVL树,我们就必须先说二叉查找树,先介绍二叉查找树的一些特性,然后我们再来说平衡树的一些特性,结合这些特性,然后来介绍AVL树. 一.二叉查找树 1.二叉树查找树的相关特征定义 二叉树查找树 ...

  3. 判断AVL树是否平衡

    AVL树是高度的平衡二插搜索树,其左子树和右子树的高度之差不超过1(树中的左子树和右子树都是AVL树),维持这个高度之差就要控制它的平衡因子.那么判断一颗AVL树是否平衡就需要判断它的左子树和右子树高 ...

  4. AVL树 高度平衡的二叉查找树

    1.What is AVL tree? AVL tree 是一种特殊的二叉查找树,,首先我们要在树中引入平衡因子balance,表示结点右子树的高度减去左子树的高度差(右-左),对于一棵AVL树要么它 ...

  5. AVL树 & 重平衡概念

    AVL树是有平衡条件的二叉搜索树.这个平衡条件必须容易保持,而且需要保证树的深度是O(logN). AVL=BBST 作为二叉搜索树的最后一部分,我们来介绍最为经典的一种平衡二叉搜索树:AVL树.回顾 ...

  6. 二叉树之AVL树的平衡实现(递归与非递归)

    这篇文章用来复习AVL的平衡操作,分别会介绍其旋转操作的递归与非递归实现,但是最终带有插入示例的版本会以递归呈现. 下面这张图绘制了需要旋转操作的8种情况.(我要给做这张图的兄弟一个赞)后面会给出这八 ...

  7. leecode 树的平衡判定 java

    以前写过c++版本的,感觉java写的好舒心啊/** * Definition for binary tree * public class TreeNode { * int val; * TreeN ...

  8. Java数据结构和算法(七)--AVL树

    在上篇博客中,学习了二分搜索树:Java数据结构和算法(六)--二叉树,但是二分搜索树本身存在一个问题: 如果现在插入的数据为1,2,3,4,5,6,这样有序的数据,或者是逆序 这种情况下的二分搜索树 ...

  9. AVL树(三)之 Java的实现

    概要 前面分别介绍了AVL树"C语言版本"和"C++版本",本章介绍AVL树的Java实现版本,它的算法与C语言和C++版本一样.内容包括:1. AVL树的介绍 ...

随机推荐

  1. 差分进化算法 DE-Differential Evolution

    差分进化算法 (Differential Evolution)   Differential Evolution(DE)是由Storn等人于1995年提出的,和其它演化算法一样,DE是一种模拟生物进化 ...

  2. Excel——使用OFFSET、MATCH、COUNTA实现二级菜单

    如图所示,接下来提供两种办法实现: 1.将A.B.C.D定义为名称NAME. 2.设置一级菜单单元格数据有效性为NAME. 3.设置二级菜单格数据有效为: =OFFSET($A$1,MATCH($A6 ...

  3. 用Model-View-ViewModel构建iOS App

    如果你已经开发一段时间的iOS应用,你一定听说过Model-View-Controller,即MVC.MVC是构建iOS App的标准模式.然而,最近我已经越来越厌倦MVC的一些缺点.在本文,我将重温 ...

  4. [Python] from scipy import sparse 报 DLL load failed:找不到指定模块错误

    依赖vc运行环境.需要安装 vc_redist Py3.5要安装2015版 传送门: https://www.microsoft.com/zh-CN/download/details.aspx?id= ...

  5. mac php环境启动

    mac 环境下,用brew安装php相关环境启动命令 说明 这里php,mysql,nginx都是用brew安装,安装目录默认,在Cellar下面 php-fpm 带配置重启 /*注意权限,加 sud ...

  6. winform 跨线程操作控件

    当进行winform的开发时,经常遇到用时比较久的操作,在传统的单线程程序中,用户必须等待这个耗时操作完成以后才能进行下一步的操作,这个时候,多线程编程就派上用场了,将这个耗时的操作放到一个新的子线程 ...

  7. 安装windows下的Cscope

    http://blog.csdn.net/maxiee/article/details/10034263 Cscope 是一款用于查看大型工程中的代码的软件.它使用方便,支持快速查找 C Symbol ...

  8. Volley框架使用笔记

    1.初始化请求队列 RequestQueue RequestQueue queue= Volley.newRequestQueue(context); 2.StringRequest 网络请求 Get ...

  9. .htaccess中Apache配置详解

    1.<IfDefine> 指令 说明 封装一组只有在启动时当测试结果为真时才生效的指令 语法 <IfDefine [!]parameter-name> ... </IfD ...

  10. Spring MVC返回Map格式JSON数据

    问题描述: ajax中走error : function(e) {} 问题背景: 在测试controller层时,试过了ResponseEntity<ResponseModel>这种类型返 ...