本文根据《大话数据结构》一书及网络资料,实现了Java版的平衡二叉树(AVL树)。html
在上篇博客中所实现的二叉排序树(二叉搜索树),其查找性能取决于二叉排序树的形状,当二叉排序树比较平衡时(深度与彻底二叉树相同,[log2n]+1),时间复杂度为O(logn);但也有可能出现极端的斜树,如依照{35,37,47,51,58,62,73,88,91,99}的顺序,构建的二叉排序树就以下图所示,查找时间复杂度为O(n)。java
图1 斜树node
为提升查找复杂度,在二叉排序树的基础上,提出了二叉平衡树:一种二叉排序树,其中每一个结点的左右子树的高度差至多等于1。算法
图2 平衡二叉树与非平衡二叉树网络
定义二叉树结点的左子树深度减去右子树深度的值为平衡因子BF(Balance Factor),平衡树全部结点的BF只能是-1,0,1。数据结构
距离新插入结点最近,且平衡因子的绝对值大于1的结点为根的子树,称为最小不平衡子树。ide
构建平衡二叉树的基本思想就是:在构建过程当中,每当插入一个结点时,检查是否破坏了树的平衡性,如果,则找出最小不平衡树,进行相应的调整。函数
具体实现步骤不少地方都有介绍,本文再也不赘述。post
二叉树的结点结构定义:性能
private class AVLnode { int data; // 结点数据 int bf; // 平衡因子,左高记为1,右高记为-1,平衡记为0 AVLnode lChild, rChild; // 左右孩子 public AVLnode(int data) { this.data = data; bf = 0; lChild = null; rChild = null; } }
根据以前提到的基本思想,为调整最小不平衡树,首先要了解两种最基本的操做:左旋操做和右旋操做。
(1)右旋
以下图中左边的最小不平衡二叉树,进行右旋操做便可变为右边中的平衡二叉树。
图3 右旋操做(状况1)
根据上图,容易编写右旋操做的代码以下:
/* * 右旋 * 返回新的根结点 */ public AVLnode rRotate(AVLnode p) { AVLnode l = p.lChild; p.lChild = l.rChild; l.rChild = p; return l; }
(2)左旋操做
同上所述,左旋操做的图示及代码,以下所示。
图4 左旋操做
/* * 左旋 * 返回新的根结点 */ public AVLnode lRotate(AVLnode p) { AVLnode r = p.rChild; p.rChild = r.lChild; r.lChild = p; return r; }
对于最小不平衡子树,若其左子树深度比右子树大2(下面称为左斜的不平衡树),需进行左平衡旋转操做。若右子树深度大,则需进行右平衡旋转操做。
(1)左平衡旋转:
左斜的不平衡树有几种形式,下面分开讨论
>> L结点的BF值为1时
直接对根结点P右旋便可
状况(1):以下图所示,右旋根结点P。平衡后,P结点的BF值为0,其左结点L的BF值也为0。
图5 状况(1)
>> L结点的BF值为-1时
都是先对L结点左旋,再对P结点右旋。根据平衡后P结点和L结点的BF值不一样,能够分出下面三种状况:
状况(2):以下图所示,先左旋L结点,再右旋P结点。平衡后,P结点的BF值为-1,L结点的BF值为0,LR结点的BF值为0。
图6 状况(2)
(注:示意图中,小三角形表示的子树比大三角形表示的子树深度少1,下同)
状况(3):以下图所示,先左旋L结点,再右旋P结点。平衡后,P结点的BF值为0,L结点的BF值为1,LR结点的BF值为0。
图7 状况(3)
状况(4):以下图所示,先左旋L结点,再右旋P结点。平衡后,P结点的BF值为0,L结点的BF值为0,LR结点的BF值为0。
图8 状况(4)
>> L结点的BF值为0时
最小不平衡子树也可能出现下面这种状况(插入时不会出现,但删除操做过程当中可能出现),《大话》一书中没有讨论到这种状况。
状况(5):以下图所示,直接右旋P结点。平衡后,L结点的BF值为-1,LR结点的BF值为1。
图9 状况(5)
综上所述,左平衡旋转一共可能出现5种状况,如下为左平衡旋转操做的代码:
/* * 左平衡旋转(左子树高度比右子树高2时(左斜)执行的操做) * 返回值为新的根结点 */ public AVLnode leftBalance(AVLnode p) { AVLnode l = p.lChild; switch (l.bf) { case 1: // 情況(1) p.bf = 0; l.bf = 0; return rRotate(p); case -1: AVLnode lr = l.rChild; switch (lr.bf) { case 1: // 情況(2) p.bf = -1; l.bf = 0; break; // break别漏写了 case -1: // 情況(3) p.bf = 0; l.bf = 1; break; case 0: // 情況(4) p.bf = 0; l.bf = 0; break; } lr.bf = 0; // 设置好平衡因子bf后,先左旋 p.lChild = lRotate(l);// 不能用l=leftBalance(l); // 再右旋 return rRotate(p); case 0: // 这种状况书中没有考虑到,状况(5) l.bf = -1; p.bf = 1; return rRotate(p); } // 如下状况应该是不会出现的,全部状况都已经包括,除非程序还有问题 System.out.println("bf超出范围,请检查程序!"); return p; }
(2)右平衡旋转:
与左平衡的分析相似,也能够分为五种状况,再也不赘述,下面直接给出代码:
/* * 右平衡旋转(右子树高度比左子树高2时执行的操做) * 返回值为新的根结点 */ public AVLnode rightBalance(AVLnode p) { AVLnode r = p.rChild; switch (r.bf) { case -1: p.bf = 0; r.bf = 0; return lRotate(p); case 1: AVLnode rl = r.lChild; switch (rl.bf) { case 1: r.bf = -1; p.bf = 0; break; case -1: r.bf = 0; p.bf = 1; break; case 0: r.bf = 0; p.bf = 0; break; } rl.bf = 0; p.rChild = rRotate(r); return lRotate(p); case 0: p.bf = -1; r.bf = 1; return lRotate(p); } // 如下状况应该是不会出现的,全部状况都已经包括,除非程序还有问题 System.out.println("bf超出范围,请检查程序!"); return p; }
二叉平衡树是一种二叉排序树,因此其操做与二叉排序树相同,但为了保持平衡,须要对平衡度进行分析。
引入一个变量taller来衡量子树是否长高,若子树长高了,就必须对平衡度进行分析:若是不平衡,就进行上面所说的左右平衡旋转操做。
具体的Java实现代码以下:
/* * 插入操做 * 要多定义一个taller变量 */ boolean taller;// 树是否长高 public void insert(int key) { root = insert(root, key); } private AVLnode insert(AVLnode tree, int key) {// 二叉查找树的插入操做同样,但多了树是否长高的判断(树没长高就彻底相似BST二叉树),要记得每次对taller赋值 if (tree == null) { taller = true; return new AVLnode(key); } if (key == tree.data) { System.out.println("数据重复,没法插入!"); taller = false; return tree; } else if (key < tree.data) { tree.lChild = insert(tree.lChild, key); if (taller == true) { // 左子树长高了,要对tree的平衡度分析 switch (tree.bf) { case 1: // 本来左子树比右子树高,须要左平衡处理 taller = false; // 左平衡处理,高度没有增长 return leftBalance(tree); case 0: // 本来左右子树等高,现因左子树增高而增高 tree.bf = 1; taller = true; return tree; case -1: // 本来右子树比左子树高,现左右子树相等 tree.bf = 0; taller = false; return tree; } } } else if (key > tree.data) { tree.rChild = insert(tree.rChild, key); if (taller == true) { // 右子树长高了,要对tree的平衡度分析 switch (tree.bf) { case 1: // 本来左子树高,现等高 tree.bf = 0; taller = false; return tree; case 0: // 本来等高,现右边增高了 tree.bf = -1; taller = true; return tree; case -1: // 本来右子树高,需右平衡处理 taller = false; return rightBalance(tree); } } } return tree; }
AVL树的完整代码以下(含测试代码):
package AVLTree; /** * AVL树 * @author Yongh * */ public class AVLTree { private AVLnode root; private class AVLnode { int data; // 结点数据 int bf; // 平衡因子,左高记为1,右高记为-1,平衡记为0 AVLnode lChild, rChild; // 左右孩子 public AVLnode(int data) { this.data = data; bf = 0; lChild = null; rChild = null; } } /* * 右旋 * 返回新的根结点 */ public AVLnode rRotate(AVLnode p) { AVLnode l = p.lChild; p.lChild = l.rChild; l.rChild = p; return l; } /* * 左旋 * 返回新的根结点 */ public AVLnode lRotate(AVLnode p) { AVLnode r = p.rChild; p.rChild = r.lChild; r.lChild = p; return r; } /* * 左平衡旋转(左子树高度比右子树高2时(左斜)执行的操做) * 返回值为新的根结点 */ public AVLnode leftBalance(AVLnode p) { AVLnode l = p.lChild; switch (l.bf) { case 1: // 情況(1) p.bf = 0; l.bf = 0; return rRotate(p); case -1: AVLnode lr = l.rChild; switch (lr.bf) { case 1: // 情況(2) p.bf = -1; l.bf = 0; break; // break别漏写了 case -1: // 情況(3) p.bf = 0; l.bf = 1; break; case 0: // 情況(4) p.bf = 0; l.bf = 0; break; } lr.bf = 0; // 设置好平衡因子bf后,先左旋 p.lChild = lRotate(l);// 不能用l=leftBalance(l); // 再右旋 return rRotate(p); case 0: // 这种状况书中没有考虑到,状况(5) l.bf = -1; p.bf = 1; return rRotate(p); } // 如下状况应该是不会出现的,全部状况都已经包括,除非程序还有问题 System.out.println("bf超出范围,请检查程序!"); return p; } /* * 右平衡旋转(右子树高度比左子树高2时执行的操做) * 返回值为新的根结点 */ public AVLnode rightBalance(AVLnode p) { AVLnode r = p.rChild; switch (r.bf) { case -1: p.bf = 0; r.bf = 0; return lRotate(p); case 1: AVLnode rl = r.lChild; switch (rl.bf) { case 1: r.bf = -1; p.bf = 0; break; case -1: r.bf = 0; p.bf = 1; break; case 0: r.bf = 0; p.bf = 0; break; } rl.bf = 0; p.rChild = rRotate(r); return lRotate(p); case 0: p.bf = -1; r.bf = 1; return lRotate(p); } // 如下状况应该是不会出现的,全部状况都已经包括,除非程序还有问题 System.out.println("bf超出范围,请检查程序!"); return p; } /* * 插入操做 * 要多定义一个taller变量 */ boolean taller;// 树是否长高 public void insert(int key) { root = insert(root, key); } private AVLnode insert(AVLnode tree, int key) {// 二叉查找树的插入操做同样,但多了树是否长高的判断(树没长高就彻底相似BST二叉树),要记得每次对taller赋值 if (tree == null) { taller = true; return new AVLnode(key); } if (key == tree.data) { System.out.println("数据重复,没法插入!"); taller = false; return tree; } else if (key < tree.data) { tree.lChild = insert(tree.lChild, key); if (taller == true) { // 左子树长高了,要对tree的平衡度分析 switch (tree.bf) { case 1: // 本来左子树比右子树高,须要左平衡处理 taller = false; // 左平衡处理,高度没有增长 return leftBalance(tree); case 0: // 本来左右子树等高,现因左子树增高而增高 tree.bf = 1; taller = true; return tree; case -1: // 本来右子树比左子树高,现左右子树相等 tree.bf = 0; taller = false; return tree; } } } else if (key > tree.data) { tree.rChild = insert(tree.rChild, key); if (taller == true) { // 右子树长高了,要对tree的平衡度分析 switch (tree.bf) { case 1: // 本来左子树高,现等高 tree.bf = 0; taller = false; return tree; case 0: // 本来等高,现右边增高了 tree.bf = -1; taller = true; return tree; case -1: // 本来右子树高,需右平衡处理 taller = false; return rightBalance(tree); } } } return tree; } /* * 前序遍历 */ public void preOrder() { preOrderTraverse(root); System.out.println(); } private void preOrderTraverse(AVLnode node) { if (node == null) return; System.out.print(node.data+" "); preOrderTraverse(node.lChild); preOrderTraverse(node.rChild); } /* * 中序遍历 */ public void inOrder() { inOrderTraverse(root); System.out.println(); } private void inOrderTraverse(AVLnode node) { if (node == null) return; inOrderTraverse(node.lChild); System.out.print(node.data+" "); inOrderTraverse(node.rChild); } /* * 测试代码 */ public static void main(String[] args) { AVLTree aTree = new AVLTree(); int[] arr = { 3, 2, 1, 4, 5, 6, 7, 10, 9, 8 }; for (int i : arr) { aTree.insert(i); } System.out.print("前序遍历结果:"); aTree.preOrder(); System.out.print("中序遍历结果:"); aTree.inOrder(); AVLTree bTree = new AVLTree(); int[] arr2 = { 3,2,1,4,5,6,7,16,15,14,13,12,11,10,8,9 }; for (int i : arr2) { bTree.insert(i); } System.out.print("前序遍历结果:"); bTree.preOrder(); System.out.print("中序遍历结果:"); bTree.inOrder(); } }
前序遍历结果:4 2 1 3 7 6 5 9 8 10 中序遍历结果:1 2 3 4 5 6 7 8 9 10 前序遍历结果:7 4 2 1 3 6 5 13 11 9 8 10 12 15 14 16 中序遍历结果:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
测试代码中的两个AVL树以下图所示:
图10 aTree
图11 bTree
后记
若是不用平衡因子BF,而是子树的高度来进行分析,讨论的状况就比较少,可参考这篇博客:AVL树(三)之 Java的实现