why? what? when? how?
最近几天看了某个大佬写的学习总结,觉得这个方式不错就引进了。
why
为什么要用平衡二叉树?
二叉搜索树
二叉搜索树的查找效率和 BST 建立的时候节点输入顺序相关。
1. 若输入节点顺序 1、2、3、4、5
树的深度为 5,查找效率 O(N),平均查找长度 ASL = ( 1+2+3+4+5 ) / 5 = 3
2. 若输入节点顺序 4、3、5、1、2
该树是完全二叉树,树的深度是 3,查找效率是 O(log2n), ASL = ( 1+22+32 ) / 5 = 2.1
因为 BST 的查询效率和节点输入的顺序有很大联系最坏情况是 O(N),为了提高查询效率所以平衡二叉树(查找的时间复杂度 O(log2N) ―― 证明见定义后面)出现了。
what
什么是平衡二叉树?
平衡因子( Balance Factor,简称 BF ):BF(T)=hL-hR,其中hL和hR分别为T的左、右子树的高度。
平衡二叉树 ( Balanced Binary Tree ) ( AVL树 ) 空树,或者任一结点左、右子树高度差的绝对值不超过1,即 |BF(T)| <= 1 .
平衡二叉树的高度能达到 log2N 吗? 如果能到达那么相比 BST 查询效率会高很多。
证明:设nh高度为h的平衡二叉树的最少结点数。结点数最少时:
若结点一个 1 个的时候树高为 0(我上面是为 1 设为 0 主要是为了下面好看些)
当树高为 0: 最少结点数 1
当树高为 1: 最少结点数 2
当树高为 2: 最少结点数 4
当树高为 3: 最少结点数 7
这个和斐波那契数列(Fibonacci sequence)有点像
when
什么时候调整平衡二叉树(插入删除满足什么样的状态时)?
当插入或删除时 |BF(T)| > 1。
how
如何调整平衡二叉树?
LL,RR,LR,RL 旋转
以前学的时候对 LL,RR,LR,RL 旋转不是很理解为什么这样叫?
插入结点 2 后
不平衡了,“发现者”是结点4,“麻烦结点" 2 在结点 4的左边的左边。
旋转调整
“发现者”是结点4,“麻烦结点" 2 在结点 4的左子树的左边叫 LL 插入,需要 LL 旋转(往右转一下)。
旋转调整
"麻烦结点" 6 在"发现者" 4 的右子树的右边叫 RR 插入,需要 RR 旋转(往左转一下)。
旋转调整
"麻烦结点" 4 在"发现者" 6 左子树的右边所以叫 LR 插入,需要 LR 旋转(先右转再左转)。
旋转调整
"麻烦结点" 5 在"发现者" 2 右子树的左边所以叫 RL 插入,需要 RL 旋转(先左转再右转)。
class AVLNode{ Integer data; //存放数据 AVLNode leftNode; //左孩子 AVLNode rightNode; //右孩子 int height; //树高 }
LL
//右单旋转 public AVLNode singleRightRotation(AVLNode node) { //node 有左孩子 //将node 和其左孩子右转,并更新高度,返回新的根节点 AVLNode temp = node.leftNode; node.leftNode = temp.rightNode; temp.rightNode = node; node.height = max(getHeight(node.leftNode), getHeight(node.rightNode)) + 1; temp.height = max(getHeight(temp.leftNode), node.height) + 1; return temp; }
RR
public AVLNode singleLeftRotation(AVLNode node) { //node 有右孩子 //将node 和其右孩子左转,并更新高度,返回新的根节点 AVLNode temp = node.rightNode; node.rightNode = temp.leftNode; temp.leftNode = node; node.height = max(getHeight(node.leftNode), getHeight(node.rightNode)) + 1; temp.height = max(getHeight(temp.rightNode), node.height) + 1; return temp; }
LR
public AVLNode doubleLeftRightRotation(AVLNode node){ //node有个左孩子 B,B有其右孩子 //B、C左转,C、node右转 //B C 左单转 node.leftNode=singleLeftRotation(node.leftNode); //C node 右单转 return singleRightRotation(node); }
RL
public AVLNode doubleRightLeftRotation(AVLNode node){ //node 有个右孩子 B,B有其左孩子 //B C 右转,c node 左转 //B C 右单转 node.rightNode=singleRightRotation(node.rightNode); //C node 左单转 return singleLeftRotation(node); }
//插入结点 public AVLNode avlInsert(AVLNode node, int val) { if (node == null) { node = new AVLNode(); node.data = val; node.leftNode = node.rightNode = null; } //插入左边 if (val < node.data) { node.leftNode = avlInsert(node.leftNode, val); if (getHeight(node.leftNode) - getHeight(node.rightNode) == 2) { //LL if (val < node.leftNode.data) { node = singleRightRotation(node); } else { //LR node = doubleLeftRightRotation(node); } } } else if (val > node.data) { node.rightNode = avlInsert(node.rightNode, val); if (getHeight(node.leftNode) - getHeight(node.rightNode) == -2) { //RR if (val > node.rightNode.data) { node = singleLeftRotation(node); } else { //RL node = doubleRightLeftRotation(node); } } } else { //相等不需要任何操作 } //更新树高 node.height = max(getHeight(node.leftNode), getHeight(node.rightNode)) + 1; return node; }
删除是平衡二叉树操作中最繁琐的步骤了需要核查删除结点父亲结点及以上父亲结点是否平衡。不过具体情况可以分为 3 种。
- 删除的结点是叶子节点
- 删除的结点含有左孩子或右孩子
- 删除的结点含有左孩子和右孩子
当删除结点后需要检索推算父结点或祖先结点是否失衡!!!
public AVLNode avlDelete(AVLNode node,int val){ //指向前驱、后继(当删除结点有左孩子和右孩子) AVLNode pre,post; //没有找到要删除的结点 if(node==null){ System.out.println("没有找到要删除的结点"); }else if(node.data==val){ //需要删除的结点是叶子节点 if(node.leftNode==null&&node.rightNode==null){ node=null; }else if(node.rightNode==null){ //当要删除的结点右子树为空时 node=node.leftNode; }else if(node.leftNode==null){ //当要删除的结点左子树为空时 node=node.rightNode; }else{ //左右子树都不为空时 //删除前驱(中序遍历) pre=node.leftNode; while(pre.rightNode!=null) { pre = pre.rightNode; } node.data=pre.data; //为什么要用node.leftNode,因为node.data==pre.data node.leftNode=avlDelete(node.leftNode,pre.data); //重新判断树高 node.height=max(getHeight(node.leftNode),getHeight(node.rightNode))+1; //判断是否需要调整 if(-2==getHeight(node.leftNode)-getHeight(node.rightNode)){ if(getHeight(node.rightNode.leftNode)>getHeight(node.rightNode.rightNode)){ //RL旋转 node=doubleRightLeftRotation(node); }else{ //RR旋转 node=singleLeftRotation(node); } } // //删除后继(中序遍历) // post=node.rightNode; // while(post.leftNode!=null){ // post=post.leftNode; // } // node.data=post.data; // node.rightNode=avlDelete(node.rightNode,post.data); // node.height=max(getHeight(node.leftNode),getHeight(node.rightNode))+1; // if(2==getHeight(node.leftNode)-getHeight(node.rightNode)){ // if(getHeight(node.leftNode.leftNode)>getHeight(node.leftNode.rightNode)){ // //LL旋转 // node=singleRightRotation(node); // }else{ // //LR旋转 // node=doubleLeftRightRotation(node); // } // } } } //在左子树中递归删除 else if(val<node.data){ node.leftNode=avlDelete(node.leftNode,val); node.height=max(getHeight(node.leftNode),getHeight(node.rightNode))+1; //判断是否需要进行旋转以保持二叉平衡树的特性 if(-2==getHeight(node.leftNode)-getHeight(node.rightNode)){ if(getHeight(node.rightNode.leftNode)>getHeight(node.rightNode.rightNode)){ //RL旋转 node=doubleRightLeftRotation(node); }else{ //RR旋转 node=singleLeftRotation(node); } } } //在右子树中递归删除 else { node.rightNode=avlDelete(node.rightNode,val); node.height=max(getHeight(node.leftNode),getHeight(node.rightNode))+1; //判断是否需要进行旋转以保持二叉平衡树的特性 if(2==getHeight(node.leftNode)-getHeight(node.rightNode)){ if(getHeight(node.leftNode.leftNode)>getHeight(node.leftNode.rightNode)){ //LL旋转 node=singleRightRotation(node); }else{ //LR旋转 node=doubleLeftRightRotation(node); } } } return node; }
完整代码
https://github.com/rookieLJ/AVLTree.git
参考
陈越 何钦铭 数据结构
https://www.cnblogs.com/Camilo/p/3917041.html
平衡二叉树是建立在二叉搜索树基础上为了保存查找效率 O(log2N)查找次数小于等于树高,但是插入和删除结点会调整树结构所以插入和删除的效率无法保证。
使用 what? why? when? how?学习总结感觉效果非常好,思路清晰很多十分推荐!!!!
才学疏浅,有什么问题请大家指出来。十分感谢!
原文:https://www.cnblogs.com/rookieJW/p/9273120.html