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

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

2,为什么需要AVL树呢?在二叉查找树中最坏情况下查找某个元素的时间复杂度为O(n),而AVL树能保证查找操作的时间复杂度总为O(logn)。

3,AVL树的JAVA代码实现:

AVLTree  继承 BinarySearchTree 并改写 添加节点的add方法,在add方法中判断插入元素后是否导致树不平衡,当不平衡时需要通过旋转进行调整。何时进行旋转调整是通过左右子树的高度之差进行判断的。这里通过rebalance方法使得某结点保持平衡:整个程序的完成代码参考最后。

private BinaryNodeInterface<T> rebalance(BinaryNodeInterface<T> nodeN){
        int heightDifference = getHeightDifference(nodeN);
        if(heightDifference > 1){//左子树比右子树高
            if(getHeightDifference(nodeN.getLeftChild()) > 0)
                nodeN = rotateRight(nodeN);//插入在左子树的左孩子中
            else
                nodeN = rotateLeftRight(nodeN);
        }
        else if(heightDifference < -1){//右子树比左子树高
            if(getHeightDifference(nodeN.getRightChild()) < 0)
                nodeN = rotateLeft(nodeN);//插入在右子树的右孩子中
            else
                nodeN = rotateRightLeft(nodeN);
        }
        return nodeN;
    }

4,AVL树的平衡保证算法

当向AVL树中插入或者删除元素时,可能打破树的平衡。这时,需要通过旋转来调整使之重新变成一颗AVL树。(这里讨论插入元素时的调整)

设 N 表示最接近新叶子的不平衡结点,由于插入元素之前树是平衡的,则插入之后不会有比 N 更高的不平衡结点。(树根的高度为 1 )

一共有 4 种原因导致结点 N 不平衡:

①在 结点 N 的左孩子的左子树中发生了插入操作

进行右旋转调整:即对结点 N 进行右旋转,算法如下:

算法 rotateRight(nodeN)
nodeL = nodeN 的左孩子
将nodeN 的左孩子置为 nodeL 的右孩子
将 nodeL 的右孩子置为 nodeN
return nodeL

②在 结点N 的右孩子的右子树中发生了插入操作

进行左旋转调整:即对结点N进行左旋转,算法如下:

算法 rotateLeft(nodeN)
nodeR = nodeN 的右孩子
将 nodeN 的右孩子置为 nodeR 的左孩子
将 nodeR 的左孩子置为 nodeN
return nodeR

③在 结点N 的右孩子的左子树中发生了插入操作

进行右-左旋转,即先对结点N 的孙子(孩子的孩子,"位于新插入元素的那个方向")进行右旋转;再对结点N 的新孩子进行左旋转,算法如下:

算法 rotateRightLeft(nodeN)
nodeR = nodeN 的右孩子
将 nodeN 的右孩子置为由 rotateRight(nodeN 的孩子的孩子)返回的结点
return rotateLeft(nodeN 的新的右孩子)

④在 结点N 的左孩子的右子树中发生了插入操作

进行左-右旋转,即先对结点N的孙子进行左旋转,再对结点N的新孩子进行右旋转,算法如下:

算法 rotateLeftRight(nodeN)
nodeL = nodeN 的左孩子
将 nodeN 的左孩子置为由 rotateLeft(nodeN 孩子的孩子)返回的结点
return rotateLeft(nodeN 新的左孩子)

5,关于树的平衡性的进一步讨论

除了AVL树之外,还有2-3树、2-4树、红黑树等一系列带有平衡性质的树。其中2-3树和2-4树是完全平衡的,即所有的叶子位于同一层。2-3树的结点至多有两个数据元素,2-4树的结点至多有三个数据元素,这样使得在相同的结点数目下,一般,AVL树比2-3树要高,2-3树比2-4树要高,但是在查找过程中内部结点的比较次数2-4树比2-3要多,2-3树比AVL树要多。因此,总体上看,AVL树、2-3树、2-4树、红黑树的查找都是O(logn)操作。维护平衡性由难到易的排列: AVL树 > 2-3树 > 2-4树 ·= 红黑树。其次,随着对于结点含有3个以上的数据元素的查找树的性能反而会降低,因此这也是为什么没有2-5树……的原因。。。

AVL树的完整JAVA代码实现:

  1 package searchtree;
  2
  3 import tree.BinaryNode;
  4 import tree.BinaryNodeInterface;
  5
  6 public class AVLTree<T extends Comparable<? super T>> extends BinarySearchTree<T> implements SearchTreeInterface<T>,java.io.Serializable {
  7     public AVLTree(){
  8         super();
  9     }
 10
 11     public AVLTree(T rootEntry){
 12         super(rootEntry);
 13     }
 14
 15     /*
 16      *@Task : 在AVL树中添加元素
 17      */
 18     public T add(T newEntry){
 19         T result = null;
 20         if(isEmpty())
 21             setRootNode(new BinaryNode<T>(newEntry));
 22         else
 23         {
 24             BinaryNodeInterface<T> rootNode = getRootNode();
 25             result = addEntry(rootNode, newEntry);
 26             setRootNode(rebalance(rootNode));
 27         }
 28         return result;
 29     }
 30
 31     private T addEntry(BinaryNodeInterface<T> rootNode, T newEntry){
 32         assert rootNode != null;
 33         T result = null;
 34         int comparison = newEntry.compareTo(rootNode.getData());//待添加元素与树中已有元素比较以确定添加的位置
 35         if(comparison == 0){//待添加元素已存在于树中
 36             result = rootNode.getData();
 37             rootNode.setData(newEntry);//将新元素替换旧元素
 38         }
 39         else if(comparison < 0){//添加到左子树中
 40             if(rootNode.hasLeftChild()){//继承递归比较
 41                 BinaryNodeInterface<T> leftChild = rootNode.getLeftChild();
 42                 result = addEntry(leftChild, newEntry);
 43                 rootNode.setLeftChild(rebalance(leftChild));
 44             }
 45             else
 46                 rootNode.setLeftChild(new BinaryNode<T>(newEntry));
 47         }
 48         else//添加到右子树中
 49         {
 50             assert comparison > 0;
 51             if(rootNode.hasRightChild()){
 52                 BinaryNodeInterface<T> rightChild = rootNode.getRightChild();
 53                 result = addEntry(rightChild, newEntry);
 54                 rootNode.setRightChild(rebalance(rightChild));
 55             }
 56             else
 57                 rootNode.setRightChild(new BinaryNode<T>(newEntry));
 58         }
 59         return result;
 60     }
 61
 62     public T remove(T newEntry){
 63         return null;//暂未实现删除操作
 64     }
 65
 66     /*
 67      * @Task: 在 nodeN 结点上进行右旋操作
 68      */
 69     private BinaryNodeInterface<T> rotateRight(BinaryNodeInterface<T> nodeN){
 70         BinaryNodeInterface<T> nodeL = nodeN.getLeftChild();
 71         nodeN.setLeftChild(nodeL.getRightChild());
 72         nodeL.setRightChild(nodeN);
 73         return nodeL;
 74     }
 75
 76     private BinaryNodeInterface<T> rotateLeft(BinaryNodeInterface<T> nodeN){
 77         BinaryNodeInterface<T> nodeR = nodeN.getRightChild();
 78         nodeN.setRightChild(nodeR.getLeftChild());
 79         nodeR.setLeftChild(nodeN);
 80         return nodeR;
 81     }
 82
 83     private BinaryNodeInterface<T> rotateRightLeft(BinaryNodeInterface<T> nodeN){
 84         BinaryNodeInterface<T> nodeR = nodeN.getRightChild();
 85         nodeN.setRightChild(rotateRight(nodeR));
 86         return rotateLeft(nodeN);
 87     }
 88
 89     private BinaryNodeInterface<T> rotateLeftRight(BinaryNodeInterface<T> nodeN){
 90         BinaryNodeInterface<T> nodeL = nodeN.getLeftChild();
 91         nodeN.setLeftChild(rotateLeft(nodeL));
 92         return rotateRight(nodeN);
 93     }
 94
 95     private BinaryNodeInterface<T> rebalance(BinaryNodeInterface<T> nodeN){
 96         int heightDifference = getHeightDifference(nodeN);
 97         if(heightDifference > 1){//左子树比右子树高
 98             if(getHeightDifference(nodeN.getLeftChild()) > 0)
 99                 nodeN = rotateRight(nodeN);//插入在左子树的左孩子中
100             else
101                 nodeN = rotateLeftRight(nodeN);
102         }
103         else if(heightDifference < -1){//右子树比左子树高
104             if(getHeightDifference(nodeN.getRightChild()) < 0)
105                 nodeN = rotateLeft(nodeN);//插入在右子树的右孩子中
106             else
107                 nodeN = rotateRightLeft(nodeN);
108         }
109         return nodeN;
110     }
111
112     //获得结点nodeN的左右子树的高度之差
113     private int getHeightDifference(BinaryNodeInterface<T> nodeN){
114         int leftHeight = 0;
115         int rightHeight = 0;
116         if(nodeN.getLeftChild() != null){
117             leftHeight = nodeN.getLeftChild().getHeight();
118         }
119         if(nodeN.getRightChild() != null){
120             rightHeight = nodeN.getRightChild().getHeight();
121         }
122         return leftHeight - rightHeight;
123     }
124 }

整个实现的依赖代码参考:https://github.com/hapjin/data-structures-and-abstraction-with-java 中的tree、searchtree、list 目录下代码。

时间: 2024-08-25 11:23:14

AVL树的JAVA实现及AVL树的旋转算法的相关文章

AVL树,红黑树,B-B+树,Trie树原理和应用

前言:本文章来源于我在知乎上回答的一个问题 AVL树,红黑树,B树,B+树,Trie树都分别应用在哪些现实场景中? 看完后您可能会了解到这些数据结构大致的原理及为什么用在这些场景,文章并不涉及具体操作(如插入删除等等) 目录 AVL树 AVL树原理与应用 红黑树 红黑树原理与应用 B/B+树 B/B+树原理与应用 Trie树 Trie树原理与应用 AVL树 简介: AVL树是最早的自平衡二叉树,在早期应用还相对来说比较广,后期由于旋转次数过多而被红黑树等结构取代(二者都是用来搜索的),AVL树内

B树、B+树、红黑树、AVL树

定义及概念 B树 二叉树的深度较大,在查找时会造成I/O读写频繁,查询效率低下,所以引入了多叉树的结构,也就是B树.阶为M的B树具有以下性质: 1.根节点在不为叶子节点的情况下儿子数为 2 ~ M2.除根结点以外的非叶子结点的儿子数为 M/2(向上取整) ~ M3.拥有 K 个孩子的非叶子节点包含 k-1 个keys(关键字),且递增排列4.所有叶子结点在同一层,即深度相同 (叶节点可以看成是一种外部节点,不包含任何关键字信息) 在B-树中,每个结点中关键字从小到大排列,并且当该结点的孩子是非叶

AVL树、红黑树以及B树介绍

简介 首先,说一下在数据结构中为什么要引入树这种结构,在我们上篇文章中介绍的数组与链表中,可以发现,数组适合查询这种静态操作(O(1)),不合适删除与插入这种动态操作(O(n)),而链表则是适合删除与插入,而查询效率则就比较慢了,本文要分享学习的树就是为了平衡这种静态操作与动态操作的差距. 一.二叉查找树 简介 满足下面条件就是二叉查找树 任意节点左子树不为空,则左子树的值均小于根节点的值. 任意节点右子树不为空,则右子树的值均大于于根节点的值. 任意节点的左右子树也分别是二叉查找树. 没有键值

B树、B+树、红黑树、AVL树比较

B树是为了提高磁盘或外部存储设备查找效率而产生的一种多路平衡查找树. B+树为B树的变形结构,用于大多数数据库或文件系统的存储而设计. B树相对于红黑树的区别 在大规模数据存储的时候,红黑树往往出现由于树的深度过大而造成磁盘IO读写过于频繁,进而导致效率低下的情况.为什么会出现这样的情况,我们知道要获取磁盘上数据,必须先通过磁盘移动臂移动到数据所在的柱面,然后找到指定盘面,接着旋转盘面找到数据所在的磁道,最后对数据进行读写.磁盘IO代价主要花费在查找所需的柱面上,树的深度过大会造成磁盘IO频繁读

AVL二叉排序树的java实现

这两天终于把AVL树好好理解了下,在<算法分析与设计基础>这本书中,被安排在变治法章节,是实例简化思想在查找树中的应用.它对平衡的要求是:每个节点的左右子树的高度差不超过1.从而我们只要在插入或删除节点时,保证这种平衡就可以了.如果平衡被打破,使用一系列旋转使树重新达到平衡. 总共有四种类型的旋转:左单转,右单转,左右双转,右左双转.只要讲到AVL树的算法书都会有旋转的解释.两个单转之间.两个双转之间相互对称.看似复杂的双转,其思想是先转换成单转,从而通过单转实现重新平衡.所以四种旋转的实现可

Trie 树 及Java实现

来源于英文“retrieval”.   Trie树就是字符树,其核心思想就是空间换时间. 举个简单的例子.   给你100000个长度不超过10的单词.对于每一个单词,我们要判断他出没出现过,如果出现了,第一次出现第几个位置.这题当然可以用hash来,但是我要介绍的是trie树.在某些方面它的用途更大.比如说对于某一个单词,我要询问它的前缀是否出现过.这样hash就不好搞了,而用trie还是很简单. 现在回到例子中,如果我们用最傻的方法,对于每一个单词,我们都要去查找它前面的单词中是否有它.那么

Java实现二叉搜索树的添加,前序、后序、中序及层序遍历,求树的节点数,求树的最大值、最小值,查找等操作

什么也不说了,直接上代码. 首先是节点类,大家都懂得 /** * 二叉树的节点类 * * @author HeYufan * * @param <T> */ class Node<T extends Comparable<? super T>> { /** * 节点储存的值 */ private T data; /** * 左子节点 */ private Node<T> leftNode; /** * 右子节点 */ private Node<T>

双数组Trie树(DoubleArrayTrie)Java实现

http://www.hankcs.com/program/java/%E5%8F%8C%E6%95%B0%E7%BB%84trie%E6%A0%91doublearraytriejava%E5%AE%9E%E7%8E%B0.html 双数组Trie树(DoubleArrayTrie)是一种空间复杂度低的Trie树,应用于字符区间大的语言(如中文.日文等)分词领域. 双数组Trie (Double-Array Trie)结构由日本人JUN-ICHI AOE于1989年提出的,是Trie结构的压缩

2-3 查找树及其Java实现

2-3 查找树 定义(来源:wiki) 查找 插入 2-3 查找树 定义(来源:wiki) 2–3树是一种树型数据结构,内部节点(存在子节点的节点)要么有2个孩子和1个数据元素,要么有3个孩子和2个数据元素,叶子节点没有孩子,并且有1个或2个数据元素. 2个结点 定义 如果一个内部节点拥有一个数据元素.两个子节点,则此节点为2节点. 如果一个内部节点拥有两个数据元素.三个子节点,则此节点为3节点. 当且仅当以下叙述中有一条成立时,T为2–3树: T为空.即T不包含任何节点. T为拥有数据元素a的