二叉树查询性能分析:
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索在二叉搜索树树平均查找长度是结点的深度的函数,即结点越深,则比较次数越多
如图:
下面就是对二叉搜索树的改进AVL树
目录:
一.AVL树的概念
二.AVL树的实现
三.AVL树的验证
四.AVL树的删除(了解)
五.AVL树的性能分析
一. AVL树的概念:
1. 二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺 序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种 解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过 1(需要对树中的结点旋转),即可降低树的高度,从而减少平均搜索长
2. 这个我们要定义一个平衡因子,平衡因子 = 右树高度 - 左树高度。
3. 如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 搜索时间复杂 度(Log2^N)。
二 AVL树的实现:
1. 为了AVL树实现简单,AVL树节点在定义时维护一个平衡因子,具体节点定义如下:
public class AVLTree { static class TreeNode{ public int val; public int bf;//平衡因子 public TreeNode left; public TreeNode right; public TreeNode parent;//父亲节点的引用 public TreeNode(int val) { this.val = val; } } public TreeNode root;
2.AVL树的插入:
2.1. 按照二叉搜索树的方式插入新节点
2.2. 调整节点的平衡因子
根据平衡因子可以分为几种情况:
情况一:Parent的平衡因子为 0,已经平衡无需调整;
情况二:Parent的平衡因子为 1,-1,继续向上调整;
情况三:Parent的平衡因子为 2,cur的平衡因子为1 ,左单旋(同号)
情况四:Parent的平衡因子为 2,cur的平衡因子为-1 ,右左双旋(异号)
情况五:Parent的平衡因子为 -2,cur的平衡因子为-1 , 右单旋(同号)
情况六:Parent的平衡因子为 -2,cur的平衡因子为 1 , 左右双旋(异号)
代码:
//插入: public boolean insert(int val) { TreeNode node = new TreeNode(val); if(root == null){ root = node; return true; } TreeNode cur = root; TreeNode parent = null; while (cur != null){ if(cur.val < val){ parent = cur; cur = cur.right; }else if(cur.val > val){ parent = cur; cur = cur.left; }else { return false; } } if(parent.val < val){ parent.right = node; }else{ parent.left = node; } node.parent = parent; cur = node;//指向新插入的节点 //修改平衡因子 while (parent != null) { //先看cur在parent的左边还是右边 if (cur == parent.right) { //如果在右边就++ parent.bf++; } else { //如果在左边就-- parent.bf--; } //检查当前的平衡因子是不是0,1,-1 if (parent.bf == 0) { //已经平衡了 break; } else if (parent.bf == 1 || parent.bf == -1) { //继续向上修改平衡因子 cur = parent; parent = cur.parent; } else { if(parent.bf == 2){//右树高-》降低右數高度 if(cur.bf == 1){ //左单旋 rotateLeft(parent); }else { //cur.bf == -1 //右左双旋: rotateRL(parent); } }else { //parent.bf == -2//左树高-》降低左树高度 if(cur.bf == -1){ //右单旋 rotateRight(parent); }else { //cur.bf == 1 //左右双旋: rotateLR(parent); } } //已经平衡 break; } } return true; }
3.AVL树的旋转:
3.1. 新节点插入较高左子树的左侧---右单旋 :
Parent的平衡因子为 -2,cur的平衡因子为-1 , 右单旋(同号)
图解:
代码:
/** * 右单旋 * @param parent */ private void rotateRight(TreeNode parent){ TreeNode subL = parent.left; TreeNode subRL = subL.right; parent.left = subRL; subL.right = parent; //如果旋转的整棵树也是一个子树,记录下原来该树的父亲,后续修改 TreeNode pParent = parent.parent; if (subRL != null) { subRL.parent = parent; } parent.parent = subL; //看看整棵树是否也是一个子树 if(parent == root){ root = subL; root.parent = null; }else { //是子树就确定这棵树是左子树还是右子树 if(pParent.left == parent){ pParent.left = subL; }else { pParent.right = subL; } } subL.parent = pParent; //修改平衡因子 subL.bf = 0; parent.bf = 0; }
3.2. 新节点插入较高右子树的右侧---左单旋:
Parent的平衡因子为 2,cur的平衡因子为1 ,左单旋(同号)
图解:
代码:
/** * 左单旋 * @param parent */ private void rotateLeft(TreeNode parent) { TreeNode subR = parent.right; TreeNode subRL = subR.left; parent.right = subRL; subR.left = parent; TreeNode pParent = parent.parent; if(subRL != null){ subRL.parent = parent; } parent.parent = subR; //看看整棵树是否也是一个子树 if(parent == root){ root = subR; root.parent = null; }else { //是子树就确定这棵树是左子树还是右子树 if(pParent.left == parent){ pParent.left = subR; }else { pParent.right = subR; } } subR.parent = pParent; parent.bf = 0; subR.bf = 0; }
3.3. Parent的平衡因子为 -2,cur的平衡因子为 1 , 左右双旋(异号):
图解:subLR的平衡因子区分情况
情况一:subLR平衡因子等于-1
情况二:subLR平衡因子等于1
代码:
/** *左右双旋: * @param parent */ private void rotateLR(TreeNode parent) { TreeNode subL = parent.left; TreeNode subLR = subL.right; int bf = subLR.bf; rotateLeft(parent.left); rotateRight(parent); //双旋时subLR的值-1,1,会有2种插入方式 if(bf == -1){ subL.bf = 0; subLR.bf = 0; parent.bf = 1; }else if(bf == 1){ subL.bf = -1; subLR.bf = 0; parent.bf = 0; } }
4. Parent的平衡因子为 2,cur的平衡因子为-1 ,右左双旋(异号):
图解:
情况一:subRL平衡因子等于1
情况二:subRL平衡因子等于-1
代码:
/** *右左双旋: * @param parent */ private void rotateRL(TreeNode parent) { TreeNode subR = parent.right; TreeNode subRL = subR.left; int bf = subRL.bf; rotateRight(parent.right); rotateLeft(parent); //双旋时subRL的值-1,1,会有2种插入方式 if(bf == 1){ parent.bf = -1; subRL.bf = 0; subR.bf = 0; }else if(bf == -1){ parent.bf = 0; subRL.bf = 0; subR.bf = 1; } }
三.AVL树的验证:
分两步:
1. 验证其为二叉搜索树:
代码:
//中序遍历: public void inorder(TreeNode root){ if(root == null){ return; } inorder(root.left); System.out.println(root.val); inorder(root.right); } //求高度 private int height(TreeNode root){ if(root == null){ return 0; } int leftH = height(root.left); int rightH = height(root.right); return Math.max(leftH,rightH) + 1; }
2. 验证其为平衡树:
//判断树是否为平衡二叉树 public boolean isBalanced(TreeNode root){ if (root == null) return true; int leftH = height(root.left); int rightH = height(root.right); /**检查这棵树平衡因子是否本身就出错 * 因为要验证的这棵树的平衡因子,是我们自己定义的,防止出错。 */ if(rightH-leftH != root.bf){ System.out.println("这个节点" + root.val+ "平衡因子异常"); return false; } return Math.abs(leftH-rightH) < 2 && isBalanced(root.left) && isBalanced(root.right); }
四.AVL树的删除(了解):
1、找到需要删除的节点
2、按照搜索树的删除规则删除节点--参考Map和Set及哈希--的奥秘(详解)-CSDN博客中二叉搜索树的删除
3、更新平衡因子,如果出现了不平衡,进行旋转。--单旋,双旋
五.AVL树性能分析:
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询 时高效的时间复杂度,即 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要 维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要 一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修 改,就不太适合