平衡二叉树(AVL树)

二叉排序树问题分析

  1. 左子树全部为空,从形式上看更像一个单链表

  2. 插入速度没有影响

  3. 查询速度明显降低

  4. 解决方案:平衡二叉树

基本介绍

  1. 平衡二叉树也叫二叉搜索树,保证查询效率较高

  2. 它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两棵子树都是一棵平衡二叉树

  3. 常用的实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等

平衡二叉树左旋转

使用条件

右子树高度与左子树高度插值大于1的时候,使用左旋转

要求

给定数列{4,3,6,5,7,8},创建对应的平衡二叉树

创建二叉排序树

此时若转换为平衡二叉树,降低右子树的高度

思路分析

  1. 创建一个新节点newNode ,值等于当前根节点的值

  2. 把新节点的左子树设置为当前节点的左子树,newnode.left = left

  3. 把新结点的右子树设置为当前节点右子树的左子树newNode.right = right.left

  4. 把当前节点的值换位右子节点的值 value = right.value

  5. 把当前节点的右子树设置成右子树的右子树right = right.right

  6. 把当前节点的左子树设置为新节点 left = newLeft

转换后

平衡二叉树右旋转

要求

使用数列{10,12,8,9,7,6},创建平衡二叉树

创建二叉排序树

基本思路

  1. 创建新的节点newNode,使得newNode.value = this.value

  2. 将newNode的右子树设置为this的右子树,newNode.right = this.right

  3. 将newNode的左子树设置为this左子树的右子树,newNode.left = this.left.right

  4. 把this节点的值换为左子节点的值,this.value = this.left.value

  5. 将this节点的左子树设置为左子树的左子树,this.left = this.left.left

  6. 将this节点的右子树 设置为newNode,this.right = newNode

转换后

平衡二叉树双旋转

要求

使用数列{10,11,7,6,8,9},创建平衡二叉树

创建二叉排序树

思路分析

  • 当孩子节点满足左旋转或右旋转条件时,先平衡孩子 节点,后平衡父节点

创建平衡二叉树代码实现

package com.why.tree;

/**
* @Description TODO 平衡二叉树
* @Author why
* @Date 2020/12/6 15:56
* Version 1.0
**/
public class AVLTreeDemo {
   public static void main(String[] args) {
       int[] arr = {10,11,7,6,8,9};
       //创建AVLTree对象
       AVLTree avlTree = new AVLTree();

       //添加节点
       for (int i = 0; i < arr.length; i++) {
           avlTree.add(new AVLNode(arr[i]));
      }

       //遍历
       System.out.println("中序遍历:");
       avlTree.midOrder();

       //根节点树的高度
       System.out.println("根节点树的高度: " + avlTree.height());
       System.out.println("左子树高度:" + avlTree.leftHeight());
       System.out.println("右子树高度:" + avlTree.rightHeight());
       System.out.println("根节点:" + avlTree.getRoot());

  }
}

/**
* AVL,平衡二叉树
*/
class AVLTree{
   private AVLNode root;

   public AVLNode getRoot() {
       return root;
  }

   public void setRoot(AVLNode root) {
       this.root = root;
  }

   /**
    * 添加节点
    * @param node
    */
   public void add(AVLNode node){
       if (root == null){//直接放上
           root = node;
      }else {
           root.add(node);
      }
  }

   /**
    * 中序遍历
    */
   public void midOrder(){
       if (root != null){
           root.midOrder();
      }else {
           System.out.println("二叉排序树为空");
      }
  }

   /**
    * 查找需删除的节点
    * @param value
    * @return
    */
   public AVLNode search(int value){
       if (root == null){
           return null;
      }else {
           return root.search(value);
      }
  }

   /**
    * 查找父节点
    * @param value
    * @return
    */
   public AVLNode searchParent(int value){
       if (root == null){
           return null;
      }else {
           return root.searchParent(value);
      }
  }

   public void deleteNode(int value){
       if (root == null){
           return;
      }else {
           //找到需删除的节点
           AVLNode targetNode = search(value);
           if (targetNode == null){//未找到
               return;
          }
           //如果二叉排序树只有一个节点
           if (root.left == null && root.right == null){
               return;
          }

           //查找需删除的节点的父节点
           AVLNode parent = searchParent(value);
           if (targetNode.left == null && targetNode.right == null){//删除的节点是叶子节点
               //判断targetNode是父节点的左子节点还是右子节点
               if (parent.left != null && parent.left.value == value){//是左子节点
                   parent.left = null;
              }else if (parent.right != null && parent.right.value == value){//是右子节点
                   parent.right = null;
              }
          }else if ((targetNode.left != null && targetNode.right == null) ||
                  (targetNode.right != null && targetNode.left == null)) {//只有一棵子树
               //确定targetNode的节点是左节点还是右节点
               if (targetNode.left != null) {//左子节点
                   if (parent != null){//非根节点
                       //确定targetNode是parent的左子节点还是右子节点
                       if (parent.left.value == value) {//左子节点
                           parent.left = targetNode.left;
                      } else {//右子节点
                           parent.right = targetNode.left;
                      }
                  }else {
                       root = targetNode.left;
                  }
              } else {//右子节点
                   if (parent != null){
                       //确定targetNode是parent的左子节点还是右子节点
                       if (parent.left.value == value) {//左子节点
                           parent.left = targetNode.right;
                      } else {//右子节点
                           parent.right = targetNode.right;
                      }
                  }else {
                       root = targetNode.right;
                  }
              }
          }else {//删除的节点有两颗子树
               //找到最小值并删除
               int minValue = deleteRightMin(targetNode.right);
               //将最小值赋值给targetNode.value
               targetNode.value = minValue;
          }
      }
  }

   /**
    * 寻找最小值
    * @param node
    * @return
    */
   public int deleteRightMin(AVLNode node){
       AVLNode target = node;
       while (target.left != null){
           target = target.left;
      }
       //这时target指向最小节点
       //删除最小节点
       deleteNode(target.value);
       //返回最小节点的value
       return target.value;
  }

   /**
    * 返回根节点树的高度
    * @return
    */
   public int height(){
       return root.height();
  }

   /**
    * 左子树的高度
    * @return
    */
   public int leftHeight(){
       return root.leftHeight();
  }

   /**
    * 右子树的高度
    * @return
    */
   public int rightHeight(){
       return root.rightHeight();
  }

}

/**
* 节点类
*/
class AVLNode{
   int value;
   AVLNode left;
   AVLNode right;

   public AVLNode(int value) {
       this.value = value;
  }

   /**
    * 添加节点,递归形式,需满足二叉排序树的要求
    * @param node
    */
   public void add(AVLNode node){
       if (node == null){
           return;
      }
       //判断传入的节点的值和当前子树的根节点的值的关系
       if (node.value < this.value){
           if (this.left == null){//当前节点左子节点为空
               this.left = node;
          }else {//不为空,递归向左子树添加
               this.left.add(node);
          }
      }else {
           if (this.right == null){
               this.right = node;
          }else {
               this.right.add(node);
          }
      }

       //当添加完节点后,若右子树的高度比左子树的高度的数值大于1
       if (rightHeight() - leftHeight() > 1){
           if (right != null && right.leftHeight() > right.rightHeight()){
               //对右子树 右旋转
               right.rightRotate();
          }
           //左旋转
           this.leftRotate();
           return;
      }
       //当添加完节点后leftHeight - rightHeight > 1
       if (leftHeight() - rightHeight() > 1){
           if (left != null && left.rightHeight() > left.leftHeight()){
               //对左子树左旋转
               left.leftRotate();
          }
           //右旋转
           this.rightRotate();
           return;
      }
  }

   /**
    * 中序遍历
    */
   public void midOrder(){
       if (left != null){
           this.left.midOrder();
      }
       System.out.println(this);
       if (this.right != null){
           this.right.midOrder();
      }
  }

   @Override
   public String toString() {
       return "Node{" +
               "value=" + value +
               '}';
  }

   /**
    * 寻找需要删除的节点
    * @param value
    * @return
    */
   public AVLNode search(int value){
       if (value == this.value){//找到
           return this;
      }else if (value < this.value){//向左子树查找
           if (this.left == null){
               return null;
          }
           return this.left.search(value);
      }else {//向右子树查找
           if (this.right == null){
               return null;
          }
           return this.right.search(value);
      }
  }

   /**
    * 查找需要删除节点的父节点
    * @param value
    * @return
    */
   public AVLNode searchParent(int value){
       if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)){
           //找到父节点返回当前节点
           return this;
      }else {
           //如果查找的值小于当前节点的值
           if (value < this.value && this.left != null){//左子树查找
               return this.left.searchParent(value);
          }else if (value >= this.value && this.right != null){//右子树查找
               return this.right.searchParent(value);
          }else {
               return null;//没有找到父节点
          }
      }
  }

   /**
    * 返回以当前节点为根节点的树的高度
    * @return
    */
   public int height(){
       return Math.max(this.left == null ? 0 : this.left.height(),this.right == null ? 0 : this.right.height()) + 1;
  }

   /**
    * 返回左子树的高度
    * @return
    */
   public int leftHeight(){
       if (left == null){
           return 0;
      }else {
           return left.height();
      }
  }

   /**
    * 返回右子树的高度
    * @return
    */
   public int rightHeight(){
       if (right == null){
           return 0;
      }else {
           return right.height();
      }
  }

   /**
    * 左旋转方法
    */
   private void leftRotate(){
       //创建新的节点,以当前根节点的值创建
       AVLNode newNode = new AVLNode(this.value);
       //把新的节点的左子树设置为当前节点的左子树
       newNode.left = this.left;
       //把新节点的右子树设置为当前节点右子树的左子树
       newNode.right = this.right.left;
       //将当前节点的值修改为右子树的值
       this.value = this.right.value;
       //将当前节点的右子树设置为右子树的右子树
       this.right = this.right.right;
       //将当前节点的左子节点设置为新的节点
       this.left = newNode;
  }

   /**
    * 右旋转
    */
   private void rightRotate(){
       //以当前节点的值创建新的节点
       AVLNode newNode = new AVLNode(this.value);
       //将新节点的右子树设置为当前节点的右子树
       newNode.right = this.right;
       //将当前节点的左子树设置为当前节点左子节点的右子树
       newNode.left = this.left.right;
       //将当前节点的值用左子节点的值替换
       this.value = this.left.value;
       //将当前节点的左子节点设置为当节点左子节点的左子树
       this.left = this.left.left;
       //将当前节点的右子节点设置为新节点
       this.right = newNode;
  }
}

所有源码都可在gitee仓库中下载:https://gitee.com/vvwhyyy/java_algorithm

Java数据结构(十四)—— 平衡二叉树(AVL树)的更多相关文章

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

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

  2. 自己动手实现java数据结构(七) AVL树

    1.AVL树介绍 前面我们已经介绍了二叉搜索树.普通的二叉搜索树在插入.删除数据时可能使得全树的数据分布不平衡,退化,导致二叉搜索树最关键的查询效率急剧降低.这也引出了平衡二叉搜索树的概念,平衡二叉搜 ...

  3. Java 树结构实际应用 四(平衡二叉树/AVL树)

    平衡二叉树(AVL 树) 1 看一个案例(说明二叉排序树可能的问题) 给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在.  左边 BST 存在的问题分析: ...

  4. 二叉查找树(BST)、平衡二叉树(AVL树)(只有插入说明)

    二叉查找树(BST).平衡二叉树(AVL树)(只有插入说明) 二叉查找树(BST) 特殊的二叉树,又称为排序二叉树.二叉搜索树.二叉排序树. 二叉查找树实际上是数据域有序的二叉树,即对树上的每个结点, ...

  5. “全栈2019”Java第九十四章:局部内部类详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  6. “全栈2019”Java第十四章:二进制、八进制、十六进制

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  7. “全栈2019”Java第二十四章:流程控制语句中决策语句switch下篇

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  8. 【数据结构】平衡二叉树—AVL树

    (百度百科)在计算机科学中,AVL树是最先发明的自平衡二叉查找树.在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树.查找.插入和删除在平均和最坏情况下都是O(log n).增 ...

  9. 数据结构快速回顾——平衡二叉树 AVL (转)

    平衡二叉树(Balanced Binary Tree)是二叉查找树的一个进化体,也是第一个引入平衡概念的二叉树.1962年,G.M. Adelson-Velsky 和 E.M. Landis发明了这棵 ...

  10. 平衡二叉树,AVL树之图解篇

    学习过了二叉查找树,想必大家有遇到一个问题.例如,将一个数组{1,2,3,4}依次插入树的时候,形成了图1的情况.有建立树与没建立树对于数据的增删查改已经没有了任何帮助,反而增添了维护的成本.而只有建 ...

随机推荐

  1. XJOI 夏令营501-511测试11 游戏

    Alice和Bob两个人正在玩一个游戏,游戏有很多种任务,难度为p的任务(p是正整数),有1/(2^p)的概率完成并得到2^(p-1)分,如果完成不了,得0分.一开始每人都是0分,从Alice开始轮流 ...

  2. Java_大体介绍(超级短的那种)

    Java三大版本 Java SE: Java Standard Edition, 定位于客户端, 用于桌面应用软件编程 Java ME: Java Micro Edition, 用于嵌入式系统开发 J ...

  3. Spring5.0源码学习系列之浅谈BeanFactory创建

    Spring5.0源码学习系列之浅谈BeanFactory创建过程 系列文章目录 提示:Spring源码学习专栏链接 @ 目录 系列文章目录 博客前言介绍 一.获取BeanFactory主流程 二.r ...

  4. 内网渗透 day4-meterpreter基本命令

    meterpreter基本命令 目录 1.getuid 查看当前用户 1 2.getpid 查看当前的进程id 1 3.getsystem 初步提权 1 4.ps 1.查看进程列表2.帮助我们获取pi ...

  5. 【涂鸦物联网足迹】API及SDK介绍

    前序系列文章>>> [涂鸦物联网足迹]物联网主流通信方式 我们系列文章,都会围绕如何完成一款智能"隔空接吻机"的开发.希望能帮到各异地恋or异国恋的情侣们! 本文 ...

  6. parted会启动你的ceph osd,意外不?

    前言 如果看到标题,你是不是第一眼觉得写错了,这个怎么可能,完全就是两个不相关的东西,最开始我也是这么想的,直到我发现真的是这样的时候,也是很意外,还是弄清楚下比较好,不然在某个操作下,也许就会出现意 ...

  7. yum 常用命令使用

    1.向服务器上传文件或者下载文件 我们知道我们经常需要向服务器上传文件,或者从服务器下载文件,rz和sz命令可以满足我们的要求, 只不过默认情况下是不能使用的.我们需要使用yum install lr ...

  8. Hive 报错 Failed to load class "org.slf4j.impl.StaticLoggerBinder".

    打开hive报错 SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaultin ...

  9. IAR_STM32_BootLoader

    1.STM32 Bootloader与APP IROM中可以分成两个区域,起始代码运行地址为0x08000000,这是基本固定的,可以将IROM的0x08000000 ~ 0x08002000这8KB ...

  10. classmethod、staticclassmethod内置装饰器函数

    # method 英文是方法的意思 # classmethod 类方法 # 当一个类中的方法中只涉及操作类的静态属性时,此时在逻辑上,我们想要直接通过类名就可以调用这个方法去修改类的静态属性,此时可以 ...