本文将经过图解和代码详细讲解AVL平衡二叉树的性质及失衡和再平衡的内容。在看本文以前但愿你们具有二分搜索树的相关知识。或移步《二分搜索树》了解二分搜索树。html
前面关于二分搜索树的文章,最后分析了在极端状况下,二分搜索树会退化为一个链表,那为了不这种状况的发生,AVL平衡二叉树应运而生。node
平衡二叉树的定义:面试
1 平衡二叉树是一颗二分搜索树,及平衡二叉树知足二分搜索树的全部性质 2 平衡二叉树要求任意一个节点的左右子树的高度差不能超过1
对于第一点应该挺容易理解的,对于第二点,咱们要作一点解释。对于高度差,有一个专有名词平衡因子。算法
平衡因子:左子树的高度减去右子树的高度,及B = B左 - B右。由平衡二叉树的定义可知,平衡因子的取值只可能为0,1,-1。0:左右子树等高。1:左子树比较高。-1:右子树比较高。以下图数据结构
高度:通常的咱们取叶子节点的高度值为1,任意一个节点的高度值取左右子树比较高的那个孩子节点的高度值而后加1。好比上图1中20这个节点的高度值,显然左子树比较高,因此H20 = H10 + 1;依次类推,H10 = H6(或者H14) + 1 = 2;因此H20 = 3;测试
上图1的树的各个节点的高度,以下图1所示。各个节点的平衡因子以下图2红色数字所示。因此根据定义,各个节点的左右子树的高度差不能超过1,及任意一个节点的平衡因子应该为 -1, 0, 1;因此下面的这棵树是一棵平衡二叉树。this
前面咱们介绍了AVL的高度和平衡因子问题,接下来咱们来看看有几种状况会致使AVL的失衡,也就是什么状况下咱们须要调整AVL树,使其通过调整后能继续维持平衡状态。spa
如上图1所示,当咱们已经插入了20,10元素,当咱们再插入6这个元素的时候,很显然20节点的平衡因子为2,及B20 = 2 > 1此时该树已经不平衡了。设计
如上图2所示,当咱们已经插入了20,10元素,当咱们再插入14这个元素的时候,很显然20节点的平衡因子为2,及B20 = 2 > 1此时该树已经不平衡了。3d
如上图3所示,当咱们已经插入了20,29元素,当咱们再插入33这个元素的时候,很显然20节点的平衡因子为-2,及B20 = -2 < -1此时该树已经不平衡了。
如上图4所示,当咱们已经插入了20,29元素,当咱们再插入25这个元素的时候,很显然20节点的平衡因子为-2,及B20 = -2 < -1此时该树已经不平衡了。
对于AVL,须要进行再平衡操做的状况正如以上4个图所示。那接下来咱们须要讨论的问题就是如何调整了,及旋转。
以下图所示,对于上一章咱们说的四种失衡状态的调整。而后咱们加下来对照着下面的四张图片进行逐一介绍旋转的过程。
LL,及对于上图1的待旋转的树(中间那棵),形成这棵树失衡的节点6在20节点的左孩子的左孩子处,left,left简称为LL。这个时候咱们须要进行的旋转通常称之为LL旋转。对于这种状况,咱们考虑如何旋转,始终要考虑如何经过旋转既达到再平衡的目的,又能维持平衡二叉树的性质不变,即左孩子 < 父节点 < 右孩子。观察图一中插入节点6之后,一个很显然的结果就是无论咱们怎么旋转只有当10节点在中间的时候咱们才能保证这棵树是平衡的。咱们知道告终果,再看看这个旋转的过程,为了保证平衡二叉树的性质,根据左孩子 < 父节点 < 右孩子的性质,咱们看20 > 10,也就是说,咱们能够将20节点下移,放到10节点的右孩子处,即获得图1中的结果。
为了更好地描述这个过程,咱们使用几个虚拟的节点。
/////////////////////////////////////////////////// // LL T1<Z<T2< X <T3<Y<T4 // // y x // // / \ / \ // // x T4 向右旋转 (y) z y // // / \ - - - - - - - -> / \ / \ // // z T3 T1 T2 T3 T4 // // / \ // // T1 T2 // ///////////////////////////////////////////////////
如上所示,咱们真实的三个节点为Y > X > Z。而后咱们为了方便描述,增长几个虚拟的节点,节点间的大小关系:T1<Z<T2< X <T3<Y<T4
对于LL,咱们要右旋才能达到再平衡,根据以前描述,咱们须要将Y节点顶替T3的位置,问题来了,T3放哪呢?根据大小关系 X < T3 < Y。咱们能够将T3放到Y的左孩子节点的位置,这样进行旋转后获得的结果如上所示。咱们发现这棵树不但达到了再平衡的目的,节点间的大小关系,依然维持了:T1<Z<T2< X <T3<Y<T4的关系。
代码实现一下这个过程,先假设咱们的节点为Node。传入的参数应该是Y节点
1 private Node rightRotate(Node y) { 2 Node x = y.left; 3 Node T3 = x.right; 4 5 // 向右旋转过程 6 x.right = y; 7 y.left = T3; 8 9 return x; 10 }
对于以上代码,结合上面咱们分析的过程,你们应该很容易就能理解。
RR对应上面的图3,RR及形成AVL失衡的节点6在20节点的右侧的右侧,即RR。对于RR咱们要进行左旋转才能实现再平衡。一样的,咱们若是想经过旋转达到再平衡,AVL树的性质依然是咱们实现这个操做的根本。如上图3所示,若是咱们将20节点移到29元素的左孩子节点处,即可实现再平衡。并且也能维持AVL树的基本性质。
同分析LL同样,咱们增长一些虚拟节点来描述这个过程。
//////////////////////////////////////////////// // RR T1<Y<T2< X <T3<Z<T4 // // y x // // / \ / \ // // T1 x 向左旋转 (y) y z // // / \ - - - - - - - -> / \ / \ // // T2 z T1 T2 T3 T4 // // / \ // // T3 T4 // ////////////////////////////////////////////////
节点间的大小关系:T1<Y<T2< X <T3<Z<T4。对于RR咱们对Y节点进行左旋转。即让Y节点顶替T2,而后根据大小关系:Y < X < T2可知,咱们能够将T2放到Y的右孩子节点处便可。对Y节点左旋完了如上图所示的结果。经过比较,节点间的大小关系,依然为:T1<Y<T2< X <T3<Z<T4。经过对Y节点的左旋转,达到了AVL的再平衡,并维持了AVL的性质不变。
代码实现就不解释了
1 private Node leftRotate(Node y) { 2 Node x = y.right; 3 Node T2 = x.left; 4 5 // 向左旋转过程 6 x.left = y; 7 y.right = T2; 8 9 return x; 10 }
LR对应上图2,即形成AVL失衡的节点14在节点20的左侧的右侧,即LR。这种状况有点复杂,并且有个很想固然的坑,就是将根节点直接换成10不就完事了?但是若是咱们这么作,发现,10的左节点为14,不知足:左孩子 < 父节点 < 右孩子的大小关系了。这种状况呢,正确的作法是先将10节点左旋,而后再将14节点右旋。你们经过以前对LL和RR的分析,在脑子中能不能想象到这个画面呢?
为了方便描述,咱们依然增长一些虚假的节点来描述这个过程。
////////////////////////////////////////////////////////////////////////////////////////// // LR T1<X<T2< Z <T3<Y<T4 // // y y z // // / \ / \ / \ // // x t4 向左旋转(x) z T4 向右旋转(y) x y // // / \ ---------------> / \ ---------------> / \ / \ // // T1 z x T3 T1 T2 T3 T4 // // / \ / \ // // T2 T3 T1 T2 // //////////////////////////////////////////////////////////////////////////////////////////
对于原始的这棵树呢,大小关系:T1<X<T2< Z <T3<Y<T4。若是咱们先不看Y节点,看X,Z,T3节点,是否是能够发现,这正是咱们上面描述的RR的状况啊。对RR,咱们上面已经进行了详细的分析,经过贵X节点进行左旋,获得中间那棵树。这时又一个神奇的事情发生了,这棵树的形状又变成了前面咱们说的,LL的状况。那你们就清楚了,对Y节点进行右旋转便可。最终的结果如上第三棵树,达到了AVL的再平衡并依然知足:T1<X<T2< Z <T3<Y<T4。
咱们发现通过咱们的分析,将这种复杂的状况进行一步步的拆解即分解成了比较简单的状况。不得不感叹一下:计算机的世界太神奇了。
呃呵,本身看吧,不解释,不接受反驳。皮一下,很开心。
////////////////////////////////////////////////////////////////////////////////////////// // RL: T1<Y<T2< Z <T3<X<T4 // // y y z // // / \ / \ / \ // // T1 x 向右旋转(x) T1 z 向左旋转(y) y x // // / \ - - - - - - -> / \ - - - - - - - - -> / \ / \ // // z T4 T2 x T1 T2 T3 T4 // // / \ / \ // // T2 T3 T3 T4 // //////////////////////////////////////////////////////////////////////////////////////////
相信你们看到这里,被面试官虐千百遍的问题,原来不过如此。其实一切高大上的问题,只要咱们耐心的看下去就能有收获。
首先须要明白,AVL的失衡是因为节点的变更引发的,也就是增和删操做才会致使节点的变更。下面咱们结合平衡因子和插入或者删除的过程,分析AVL再平衡的时机。
增长操做再平衡时机:
对于3.2章节中的过程,但愿你们能够清楚,咱们是经过眼睛观察来判断AVL是否是失衡了,可是计算机尚未达到这种能力。因此咱们想一想前面介绍的平衡因子,正是判断AVL是否是平衡的重要依据。假如如今向一棵空的AVL树依次插入[20,10,6];三个节点。当插入6节点后,以下图所示,各个节点的高度。以前说过:B = H左 - H右。咱们看一下20这个节点的平衡因子,B20 = 2 - 0 = 2 > 1;因此,这时20就是不平衡的节点,须要对20这个节点进行旋转才能再平衡。可是从元素插入操做看一下,很显然当插入6这个元素的时候,并不知道20这个节点的平衡因子已经不知足要求了。须要沿着添加的元素向上回溯,沿着该节点到根节点的路径,一步步的从新计算其父节点,爷爷节点,祖父节点...当咱们发现其父节点,爷爷节点...等平衡因子不知足要求的时候,就对该节点进行旋转。
删除操做再平衡的时机:
删除操做进行再平衡的时机相似增长操做,须要在删除节点后沿着其父节点,爷爷节点...一直向上计算各个节点的平衡因子是否知足AVL的性质。当发现某个节点的平衡因子不在[-1, 1]之间的时候,而后判断其形状对应的进行左旋转或者右旋转使其完成再平衡。
在前面的章节详细介绍了AVL的定义,失衡,再平衡即LL,RR,LR,RL等旋转。接下来咱们经过代码实现一棵AVL树。对于咱们要实现的AVL平衡二叉树,咱们期待具有的功能以下:
1 以Node做为链表的基础存储结构 2 使用泛型,并要求该泛型必须实现Comparable接口 3 基本操做:增删改查
1 /** 2 * 描述:AVL 平衡二叉树的实现 3 * 4 * @Author shf 5 * @Date 2019/7/31 15:35 6 * @Version V1.0 7 **/ 8 public class AVL<K extends Comparable<K>, V> { 9 10 private class Node{ 11 public K key; 12 public V value; 13 public Node left, right; 14 public int height;// 记录节点的高度 15 16 public Node(K key, V value){ 17 this.key = key; 18 this.value = value; 19 left = null; 20 right = null; 21 height = 1; 22 } 23 } 24 25 private Node root; 26 private int size; 27 28 public AVL(){ 29 root = null; 30 size = 0; 31 } 32 33 public int getSize(){ 34 return size; 35 } 36 37 public boolean isEmpty(){ 38 return size == 0; 39 } 40 }
在实现增删改查以前咱们先设计两个辅助方法,以下所示,getHeight方法获取节点的高度值,getBalanceFactor方法获取节点的平衡因子。
1 /** 2 * 得到节点node的高度 3 * @param node 4 * @return 5 */ 6 private int getHeight(Node node){ 7 if(node == null) 8 return 0; 9 return node.height; 10 } 11 12 /** 13 * 得到节点node的平衡因子 14 * @param node 15 * @return 16 */ 17 private int getBalanceFactor(Node node){ 18 if(node == null) 19 return 0; 20 return getHeight(node.left) - getHeight(node.right); 21 }
在《二分搜索树》介绍了二分搜索树,前面根据AVL的定义可知,AVL是彻底知足一个二分搜索树的全部性质的,若是你们想搞明白AVL,仍是建议去先去看一下二分搜索树。对于二分搜索树的添加操做的代码实现,以下所示:
1 /** 2 * 添加元素 3 * @param e 4 */ 5 public void add(E e){ 6 root = add(root, e); 7 } 8 9 /** 10 * 添加元素 - 递归实现 11 * 时间复杂度 O(log n) 12 * @param node 13 * @param e 14 * @return 返回根节点 15 */ 16 public Node add(Node node, E e){ 17 if(node == null){// 若是当前节点为空,则将要添加的节点放到当前节点处 18 size ++; 19 return new Node(e); 20 } 21 if(e.compareTo(node.e) < 0){// 若是小于当前节点,递归左孩子 22 node.left = add(node.left, e); 23 } else if(e.compareTo(node.e) > 0){// 若是大于当前节点,递归右孩子 24 node.right = add(node.right, e); 25 } 26 return node; 27 }
若是你还无法理解上面的代码,请移步《二分搜索树》。对于AVL的添加操做,无非就是在AVL中须要考虑在二分搜索树失衡的时候,如何经过旋转达到再平衡。根据咱们前面的介绍,咱们应该已经很明白旋转的思路了,那咱们直接上代码吧。
1 /** 2 * 向以node为根的二分搜索树中插入元素(key, value),递归算法 3 * 时间复杂度 O(log n) 4 * @param node 5 * @param key 6 * @param value 7 * @return 返回插入新节点后二分搜索树的根 8 */ 9 private Node add(Node node, K key, V value){ 10 11 if(node == null){ 12 size ++; 13 return new Node(key, value); 14 } 15 16 if(key.compareTo(node.key) < 0) 17 node.left = add(node.left, key, value); 18 else if(key.compareTo(node.key) > 0) 19 node.right = add(node.right, key, value); 20 else // key.compareTo(node.key) == 0 21 node.value = value; 22 23 // 更新height 24 node.height = 1 + Math.max(getHeight(node.left), getHeight(node.right)); 25 26 // 计算平衡因子 27 int balanceFactor = getBalanceFactor(node); 28 29 // 平衡维护 30 ////////////////////////////////////////////////////// 31 // LL T1<Z<T2< X <T3<Y<T4 // 32 // y x // 33 // / \ / \ // 34 // x T4 向右旋转 (y) z y // 35 // / \ - - - - - - - -> / \ / \ // 36 // z T3 T1 T2 T3 T4 // 37 // / \ // 38 // T1 T2 // 39 ////////////////////////////////////////////////////// 40 if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0) 41 return rightRotate(node); 42 ////////////////////////////////////////////////////////////////////////////////////////// 43 // LR T1<X<T2< Z <T3<Y<T4 // 44 // y y z // 45 // / \ / \ / \ // 46 // x t4 向左旋转(x) z T4 向右旋转(y) x y // 47 // / \ ---------------> / \ ---------------> / \ / \ // 48 // T1 z x T3 T1 T2 T3 T4 // 49 // / \ / \ // 50 // T2 T3 T1 T2 // 51 ////////////////////////////////////////////////////////////////////////////////////////// 52 if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) { 53 node.left = leftRotate(node.left); 54 return rightRotate(node); 55 } 56 ////////////////////////////////////////////////// 57 // RR: T1<Y<T2< X <T3<Z<T4 // 58 // y x // 59 // / \ / \ // 60 // T1 x 向左旋转 (y) y z // 61 // / \ - - - - - - - -> / \ / \ // 62 // T2 z T1 T2 T3 T4 // 63 // / \ // 64 // T3 T4 // 65 ////////////////////////////////////////////////// 66 if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0) 67 return leftRotate(node); 68 69 ////////////////////////////////////////////////////////////////////////////////////////// 70 // RL: T1<Y<T2< Z <T3<X<T4 // 71 // y y z // 72 // / \ / \ / \ // 73 // T1 x 向右旋转(x) T1 z 向左旋转(y) y x // 74 // / \ - - - - - - -> / \ - - - - - - - - -> / \ / \ // 75 // z T4 T2 x T1 T2 T3 T4 // 76 // / \ / \ // 77 // T2 T3 T3 T4 // 78 ////////////////////////////////////////////////////////////////////////////////////////// 79 if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) { 80 node.right = rightRotate(node.right); 81 return leftRotate(node); 82 } 83 84 return node; 85 }
看上面代码,辅之旋转示意图和平衡因子,相信你们能经过本身的分析,根据当前节点和左右孩子的平衡因子能判断出来是LL,LR,RR,或者RL的状况。
至于左右旋转,咱们前文给出了代码,但当咱们对节点进行旋转之后,咱们须要从新维护一下各个节点的高度值。因此通过完善的左右旋转的代码以下:
1 /** 2 * 对节点y进行向右旋转操做,返回旋转后新的根节点x 3 * @param y 4 * @return 5 */ 6 /////////////////////////////////////////////////// 7 // LL T1<Z<T2< X <T3<Y<T4 // 8 // y x // 9 // / \ / \ // 10 // x T4 向右旋转 (y) z y // 11 // / \ - - - - - - - -> / \ / \ // 12 // z T3 T1 T2 T3 T4 // 13 // / \ // 14 // T1 T2 // 15 /////////////////////////////////////////////////// 16 private Node rightRotate(Node y) { 17 Node x = y.left; 18 Node T3 = x.right; 19 20 // 向右旋转过程 21 x.right = y; 22 y.left = T3; 23 24 // 更新height 25 y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1; 26 x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1; 27 28 return x; 29 } 30 31 /** 32 * 对节点y进行向左旋转操做,返回旋转后新的根节点x 33 * @param y 34 * @return 35 */ 36 //////////////////////////////////////////////// 37 // RR T1<Y<T2< X <T3<Z<T4 // 38 // y x // 39 // / \ / \ // 40 // T1 x 向左旋转 (y) y z // 41 // / \ - - - - - - - -> / \ / \ // 42 // T2 z T1 T2 T3 T4 // 43 // / \ // 44 // T3 T4 // 45 //////////////////////////////////////////////// 46 private Node leftRotate(Node y) { 47 Node x = y.right; 48 Node T2 = x.left; 49 50 // 向左旋转过程 51 x.left = y; 52 y.right = T2; 53 54 // 更新height 55 y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1; 56 x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1; 57 58 return x; 59 }
对于删除,稍微复杂一点,可是基本思路和增长是同样的。可是关于我以为仍是有必要给你们恶补一下二分搜索树的删除的思路,当咱们删除一个节点的时候,待删除节点左右子树有一个为空,咱们只须要将其不为空的子树的根节点提到待删除元素的位置便可。若是其左右子树都不为空,则将其右子树最小的元素提到待删除节点处。详细的讨论请参阅《二分搜索树》,在二分搜索树删除操做的基础上,咱们只须要辅之再平衡操做便可。
1 /** 2 * 从二分搜索树中删除键为key的节点 3 * @param key 4 * @return 5 */ 6 public V remove(K key){ 7 8 Node node = getNode(root, key); 9 if(node != null){ 10 root = remove(root, key); 11 return node.value; 12 } 13 return null; 14 } 15 16 /** 17 * 删除指定的节点 18 * @param node 19 * @param key 20 * @return 21 */ 22 private Node remove(Node node, K key){ 23 24 if( node == null ) 25 return null; 26 27 Node retNode; 28 if( key.compareTo(node.key) < 0 ){ 29 node.left = remove(node.left , key); 30 // return node; 31 retNode = node; 32 } 33 else if(key.compareTo(node.key) > 0 ){ 34 node.right = remove(node.right, key); 35 // return node; 36 retNode = node; 37 } 38 else{ // key.compareTo(node.key) == 0 找到待删除的节点 node 39 40 // 待删除节点左子树为空,直接将右孩子替代当前节点 41 if(node.left == null){ 42 Node rightNode = node.right; 43 node.right = null; 44 size --; 45 // return rightNode; 46 retNode = rightNode; 47 } 48 49 // 待删除节点右子树为空,直接将左孩子替代当前节点 50 else if(node.right == null){ 51 Node leftNode = node.left; 52 node.left = null; 53 size --; 54 // return leftNode; 55 retNode = leftNode; 56 } 57 58 // 待删除节点左右子树均不为空的状况 59 else{ 60 // 待删除节点左右子树均不为空 61 // 找到右子树最小的元素,替代待删除节点 62 Node successor = minimum(node.right); 63 //successor.right = removeMin(node.right); 64 successor.right = remove(node.right, successor.key); 65 successor.left = node.left; 66 67 node.left = node.right = null; 68 69 // return successor; 70 retNode = successor; 71 } 72 } 73 74 if(retNode == null) 75 return null; 76 77 // 更新height 78 retNode.height = 1 + Math.max(getHeight(retNode.left), getHeight(retNode.right)); 79 80 // 计算平衡因子 81 int balanceFactor = getBalanceFactor(retNode); 82 83 // 平衡维护 84 // LL 85 if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0) 86 return rightRotate(retNode); 87 88 // RR 89 if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0) 90 return leftRotate(retNode); 91 92 // LR 93 if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) { 94 retNode.left = leftRotate(retNode.left); 95 return rightRotate(retNode); 96 } 97 98 // RL 99 if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) { 100 retNode.right = rightRotate(retNode.right); 101 return leftRotate(retNode); 102 } 103 104 return retNode; 105 } 106 /** 107 * 返回以node为根的二分搜索树的最小值所在的节点 108 * @param node 109 * @return 110 */ 111 private Node minimum(Node node){ 112 if(node.left == null) 113 return node; 114 return minimum(node.left); 115 }
1 /** 2 * 返回以node为根节点的二分搜索树中,key所在的节点 3 * @param node 4 * @param key 5 * @return 6 */ 7 private Node getNode(Node node, K key){ 8 9 if(node == null) 10 return null; 11 12 if(key.equals(node.key)) 13 return node; 14 else if(key.compareTo(node.key) < 0) 15 return getNode(node.left, key); 16 else // if(key.compareTo(node.key) > 0) 17 return getNode(node.right, key); 18 } 19 20 /** 21 * 判断是否包含 key 22 * @param key 23 * @return 24 */ 25 public boolean contains(K key){ 26 return getNode(root, key) != null; 27 } 28 29 /** 30 * 获取指定 key 的 value 31 * @param key 32 * @return 33 */ 34 public V get(K key){ 35 36 Node node = getNode(root, key); 37 return node == null ? null : node.value; 38 } 39 40 /** 41 * 设置 key 对应元素的值 value 42 * @param key 43 * @param newValue 44 */ 45 public void set(K key, V newValue){ 46 Node node = getNode(root, key); 47 if(node == null) 48 throw new IllegalArgumentException(key + " doesn't exist!"); 49 50 node.value = newValue; 51 }
前面咱们实现了一棵AVL树,咱们如何验证这究竟是不是一棵AVL树呢?这个问题,咱们依然是从其定义来思考,首先,AVL是一棵二分搜索树,其次每一个节点的平衡因子能知足在[-1, 1]之间,能知足这两点其实加之咱们代码的逻辑便可判断其是否是一棵AVL树了。
二分搜索树有一个延伸出来的性质不知道你们还记不记得,对于二分搜索树的中序遍历,实际上是对二分搜索树从小到大排序的过程。那咱们判断中序遍历的结果满不知足从小到大便可断定其是否是一棵二分搜索树。
1 /** 2 * 测试方法 - 判断该二叉树是不是一棵二分搜索树 3 * @return 4 */ 5 public boolean isBST(){ 6 7 ArrayList<K> keys = new ArrayList<>(); 8 inOrder(root, keys); 9 for(int i = 1 ; i < keys.size() ; i ++) 10 if(keys.get(i - 1).compareTo(keys.get(i)) > 0) 11 return false; 12 return true; 13 } 14 15 /** 16 * 中序遍历 17 * @param node 18 * @param keys 19 */ 20 private void inOrder(Node node, ArrayList<K> keys){ 21 22 if(node == null) 23 return; 24 25 inOrder(node.left, keys); 26 keys.add(node.key); 27 inOrder(node.right, keys); 28 } 29 30 /** 31 * 测试方法 - 判断该二叉树是不是一棵平衡二叉树 32 * @return 33 */ 34 public boolean isBalanced(){ 35 return isBalanced(root); 36 } 37 38 /** 39 * 判断以Node为根的二叉树是不是一棵平衡二叉树,递归算法 40 * @param node 41 * @return 42 */ 43 private boolean isBalanced(Node node){ 44 45 if(node == null) 46 return true; 47 48 int balanceFactor = getBalanceFactor(node); 49 if(Math.abs(balanceFactor) > 1) 50 return false; 51 return isBalanced(node.left) && isBalanced(node.right); 52 }
咱们写以下测试代码:
public class Main { public static void main(String[] args) { AVL<Integer, Integer> avl = new AVL<>(); for (int i=0; i< 10; i++){ avl.add(i, i); } System.out.println(avl.isBST()); System.out.println(avl.isBalanced()); avl.remove(5); System.out.println(avl.isBST()); System.out.println(avl.isBalanced()); } } true true true true
到此,AVL全部的内容咱们已经介绍完了。哎呦,凌晨三点了,心疼本身一秒钟。我爱个人国。
参考文献:
《玩转数据结构-从入门到进阶-刘宇波》
《数据结构与算法分析-Java语言描述》
若有错误的地方还请留言指正。
原创不易,转载请注明原文地址:https://www.cnblogs.com/hello-shf/p/11352071.html