AVL树的旋转图解和简单实现
2018-02-09 09:23
309 查看
AVL树是带有平衡条件的查找二叉树。这个平衡条件要容易保持,而且他要保证树的深度为O(logN)原文地址:http://blog.csdn.net/qq_25806863/article/details/74755131
因此一颗AVL树的条件是,对于每个节点来说,这个节点的左右子树的高度最多差1.
非AVL二叉树:
如果b是a的左子树,那么就围绕b将a向右旋转,看着就像是a直接掉下来了,掉成了b的右子树。
如果b是a的右子树,那么就围绕b将a向左旋转,看着就像是a直接掉下来了,掉成了b的左子树。
插入节点时分四种情况,四种情况对应的旋转方法是不同的:例如对于被破坏平衡的节点 a 来说:
这时是平衡的,如果在插入一个元素3,就会变成下面这样,破坏平衡:
被破坏了平衡首先要找到是哪个树被破坏了平衡,然后调整这个树。然后继续往上一个一个的调整。既然是被新插入的节点3破坏的,那么不平衡的树一定在从新插入的节点3到根节点8的路径上。找离新插入的节点最近的不平衡的树进行调整,上图中就是7.节点7的左子树 高度为1,右子树为空,高度为-1 ,不平衡。根据表格要进行右旋转。先把7这颗不平衡的树挑出来:
这棵树是最近的不平衡的树,7的左子树5高度为1,右子树为空,所以右子树高度是-1.两者的高度差达到了2,超过了1.因为左子树5的高度更高,所以要把左子树5向上提一下,这时旋转就很明显了,抓着5向上一提,7就掉到5的右边了,成了5的右子树。这个过程就是右旋:
这时继续往上找,发现每个节点都符合了平衡条件,所以整棵树就变成了AVL树。那如果节点5本来就有了右子树呢?照样右旋转,只要把原来5的右子树变成旋转后的7的左子树就行了。因为5的右子树肯定比5大,但是也肯定比7小的:
其实上面最后旋转成的树是下面这样的:
这棵树的根节点是不平衡的,还需要使用后面的双旋转来调整。<
4000
/p>使用LR先左旋后右旋调整后是这样的,具体方法看后面的:
被破坏平衡的树依然是7,但是这次就不能通过一次旋转解决了,咋转都不行。要从6开始到7进行先左旋再右旋才可以矫正平衡:
根据这个遍历可以画出这个二叉树:
一般的二叉树就不用画了,根节点为0 ,一路向右走到底。超级不平衡!参考《数据结构与算法分析java版》remove方法参考http://blog.csdn.net/liyong199012/article/details/29219261
平衡条件
一个最理想的平衡条件是左右两个子树的高度完全相等,但只有节点数量为2^n-1的树才满足这个条件(n是层数,2层要3个,3层要7个)。这个条件太严格,不好用。如果只要求根节点平衡的话,上面的条件可能会容易实现一点,但是会出现下面这样坏的二叉树:因此一颗AVL树的条件是,对于每个节点来说,这个节点的左右子树的高度最多差1.
简单示例
AVL树:非AVL二叉树:
旋转
在每一次插入数值之后,树的平衡性都可能被破坏,这时可以通过一个简单的操作来矫正平衡–旋转。旋转的目的就是减少高度,通过降低整棵树的高度来平衡。哪边的树高,就把那边的树向上旋转。通过旋转可以降低高度。所谓的左旋和右旋都是以子树为原点的:如b是a的子树,那么旋转就围绕b来进行。如果b是a的左子树,那么就围绕b将a向右旋转,看着就像是a直接掉下来了,掉成了b的右子树。
如果b是a的右子树,那么就围绕b将a向左旋转,看着就像是a直接掉下来了,掉成了b的左子树。
插入节点时分四种情况,四种情况对应的旋转方法是不同的:例如对于被破坏平衡的节点 a 来说:
插入方式 | 描述 | 旋转方式 |
---|---|---|
LL | 在a的左子树根节点的左子树上插入节点而破坏平衡 | 右旋转 |
RR | 在a的右子树根节点的右子树上插入节点而破坏平衡 | 左旋转 |
LR | 在a的左子树根节点的右子树上插入节点而破坏平衡 | 先左旋后右旋 |
RL | 在a的右子树根节点的左子树上插入节点而破坏平衡 | 先右旋后左旋 |
1.LL 右旋转
就拿最简单的举例了。一个简单的AVL树:这时是平衡的,如果在插入一个元素3,就会变成下面这样,破坏平衡:
被破坏了平衡首先要找到是哪个树被破坏了平衡,然后调整这个树。然后继续往上一个一个的调整。既然是被新插入的节点3破坏的,那么不平衡的树一定在从新插入的节点3到根节点8的路径上。找离新插入的节点最近的不平衡的树进行调整,上图中就是7.节点7的左子树 高度为1,右子树为空,高度为-1 ,不平衡。根据表格要进行右旋转。先把7这颗不平衡的树挑出来:
这棵树是最近的不平衡的树,7的左子树5高度为1,右子树为空,所以右子树高度是-1.两者的高度差达到了2,超过了1.因为左子树5的高度更高,所以要把左子树5向上提一下,这时旋转就很明显了,抓着5向上一提,7就掉到5的右边了,成了5的右子树。这个过程就是右旋:
这时继续往上找,发现每个节点都符合了平衡条件,所以整棵树就变成了AVL树。那如果节点5本来就有了右子树呢?照样右旋转,只要把原来5的右子树变成旋转后的7的左子树就行了。因为5的右子树肯定比5大,但是也肯定比7小的:
其实上面最后旋转成的树是下面这样的:
这棵树的根节点是不平衡的,还需要使用后面的双旋转来调整。<
4000
/p>使用LR先左旋后右旋调整后是这样的,具体方法看后面的:
2. RR 左旋转
在右子树的右子树上插入节点破坏的平衡需要左旋转来矫正。左旋转和右旋转类似,都是单旋转,给个流程图。3. LR 先左旋再右旋
如果在第一个例子中插入的不是3,而是6,就成了下面的样子,依然说破坏了平衡被破坏平衡的树依然是7,但是这次就不能通过一次旋转解决了,咋转都不行。要从6开始到7进行先左旋再右旋才可以矫正平衡:
4. RL 先右旋再左旋
当破坏平衡的节点是这个树的右子树的左子树时,要进行先右旋转再左旋转来矫正。同样是从破坏平衡的那个节点开始旋转,先右旋转后左旋转:简单实现
首先建立一个节点类:private static class AVLNode<E> { E element; AVLNode<E> left; AVLNode<E> right; int height; public AVLNode(E element) { this(element, null, null); } public AVLNode(E element, AVLNode<E> left, AVLNode<E> right) { this.element = element; this.left = left; this.right = right; } }以及一个插入方法insert(),删除方法remove(),求高度的方法height()整个类如下:
public class MyAVLTree<E extends Comparable<E>> { private AVLNode root; public MyAVLTree() { this.root = null; } public void insert(E x) { root = insert(x, root); } public void remove(E x) { remove(x, root); } public int height() { return height(root); } /** * 插入新数据 */ public AVLNode<E> insert(E x, AVLNode<E> t) { if (t == null) { return new AVLNode<E>(x); } //先比较 是插左边还是插右边 int compareResult = x.compareTo(t.element); if (compareResult < 0) {//插到左子树上 t.left = insert(x, t.left); //插入之后要判断是否打破了平衡,因为插入的是左子树, // 只有左子树才会打破平衡,用左子树的高减去右子树的高 if (height(t.left) - height(t.right) == 2) { //如果等于2,说明平衡被打破了,需要进行调整。就看选择什么方法调整 if (x.compareTo(t.left.element) < 0) { //如果x小于t的左子树的值,那么x会被插到t的左子树的左子树上,符合LL 用右旋转调整。 t = rightRotate(t); } else { //如果x大于t的左子树的值,则会被插到t的左子树的右子树上,符合LR,用先左旋转后右旋转来矫正。 t = leftAndRightRotate(t); } } } else if (compareResult > 0) {//插到右子树上,逻辑和上面一样。 t.right = insert(x, t.right); if (height(t.right) - height(t.left) == 2) { if (x.compareTo(t.right.element) > 0) { t = leftRotate(t); } else { t = rightAndLeftRotate(t); } } } else { //已经有这个值了 } t.height = Math.max(height(t.left), height(t.right)) + 1; return t; } /** * 删除数据 */ private AVLNode<E> remove(E x, AVLNode<E> t) { if (t == null) return null; int compareResult = x.compareTo(t.element); if (compareResult < 0) { t.left = remove(x, t.left); //完了之后验证该子树是否平衡 if (t.right != null) { //若右子树为空,则一定是平衡的,此时左子树相当对父节点深度最多为1, 所以只考虑右子树非空情况 if (t.left == null) { //若左子树删除后为空,则需要判断右子树 if (height(t.right) - t.height == 2) { AVLNode<E> k = t.right; if (k.right != null) { //右子树存在,按正常情况单旋转 t = leftRotate(t); } else { //否则是右左情况,双旋转 t = rightAndLeftRotate(t); } } } if (t.left!=null){ //否则判断左右子树的高度差 //左子树自身也可能不平衡,故先平衡左子树,再考虑整体 AVLNode<E> k = t.left; //删除操作默认用右子树上最小节点补删除的节点 //k的左子树高度不低于k的右子树 if (k.right != null) { if (height(k.left) - height(k.right) == 2) { AVLNode<E> m = k.left; if (m.left != null) { //左子树存在,按正常情况单旋转 k = rightRotate(k); } else { //否则是左右情况,双旋转 k = leftAndRightRotate(k); } } } else { if (height(k.left) - k.height == 2) { AVLNode<E> m = k.left; if (m.left != null) { //左子树存在,按正常情况单旋转 k = rightRotate(k); } else { //否则是左右情况,双旋转 k = leftAndRightRotate(k); } } } if (height(t.right) - height(t.left) == 2) { //右子树自身一定是平衡的,左右失衡的话单旋转可以解决问题 t = leftRotate(t); } } } //完了之后更新height值 t.height = Math.max(height(t.left), height(t.right)) + 1; } else if (compareResult > 0) { t.right = remove(x, t.right); //下面验证子树是否平衡 if (t.left != null) { //若左子树为空,则一定是平衡的,此时右子树相当对父节点深度最多为1 t = balanceChild(t); } //完了之后更新height值 t.height = Math.max(height(t.left), height(t.right)) + 1; } else if (t.left != null && t.right != null) { //默认用其右子树的最小数据代替该节点的数据并递归的删除那个节点 AVLNode<E> min = t.right; while (min.left != null) { min = min.left; } // t.element = findMin(t.right).element; t.element = min.element; t.right = remove(t.element, t.right); t = balanceChild(t); //完了之后更新height值 t.height = Math.max(height(t.left), height(t.right)) + 1; } else { t = (t.left != null) ? t.left : t.right; } return t; } private AVLNode<E> balanceChild(AVLNode<E> t) { if (t.right == null) { //若右子树删除后为空,则只需判断左子树与根的高度差 if (height(t.left) - t.height == 2) { AVLNode<E> k = t.left; if (k.left != null) { t = rightRotate(t); } else { t = leftAndRightRotate(t); } } } else { //若右子树删除后非空,则判断左右子树的高度差 //右子树自身也可能不平衡,故先平衡右子树,再考虑整体 AVLNode<E> k = t.right; //删除操作默认用右子树上最小节点(靠左)补删除的节点 if (k.left != null) { if (height(k.right) - height(k.left) == 2) { AVLNode<E> m = k.right; if (m.right != null) { //右子树存在,按正常情况单旋转 k = leftRotate(k); } else { //否则是右左情况,双旋转 k = rightAndLeftRotate(k); } } } else { if (height(k.right) - k.height == 2) { AVLNode<E> m = k.right; if (m.right != null) { //右子树存在,按正常情况单旋转 k = leftRotate(k); } else { //否则是右左情况,双旋转 k = rightAndLeftRotate(k); } } } //左子树自身一定是平衡的,左右失衡的话单旋转可以解决问题 if (height(t.left) - height(t.right) == 2) { t = rightRotate(t); } } return t; } /** * 右旋转 * * @param t 需要调整的树 * @return 调整后的树 */ private AVLNode<E> rightRotate(AVLNode<E> t) { AVLNode newTree = t.left; t.left = newTree.right; newTree.right = t; t.height = Math.max(height(t.left), height(t.right)) + 1; newTree.height = Math.max(height(newTree.left), height(newTree.right)) + 1; return newTree; } /** * 左旋转 */ private AVLNode<E> leftRotate(AVLNode t) { AVLNode<E> newTree = t.right; t.right = newTree.left; newTree.left = t; t.height = Math.max(height(t.left), height(t.right)) + 1; newTree.height = Math.max(height(newTree.left), height(newTree.right)) + 1; return newTree; } /** * 先左旋后右旋 */ private AVLNode<E> leftAndRightRotate(AVLNode<E> t) { t.left = leftRotate(t.left); return rightRotate(t); } /** * 先右旋后左旋 */ private AVLNode<E> rightAndLeftRotate(AVLNode<E> t) { t.right = rightRotate(t.right); return leftRotate(t); } /** * 获取指定树的高度 */ private int height(AVLNode<E> t) { return t == null ? -1 : t.height; } public void printTree() { printTree(root); } private void printTree(AVLNode<E> tree) { if (tree == null) { return; } System.out.print(tree.element + " "); printTree(tree.left); printTree(tree.right); } private static class AVLNode<E> { E element; AVLNode<E> left; AVLNode<E> right; int height; public AVLNode(E element) { this(element, null, null); } public AVLNode(E element, AVLNode<E> left, AVLNode<E> right) { this.element = element; this.left = left; this.right = right; } } }
测试
拿一般的查找二叉树和avl树进行比较,从0到9插入10个数据,打印先序遍历:public static void main(String[] args) { MySearchTree<Integer> searchTree = new MySearchTree<>(); for (int i = 0; i < 10; i++) { searchTree.insert(i); } System.out.println("```"); MyAVLTree<Integer> avlTree = new MyAVLTree<>(); for (int i = 0; i < 10; i++) { avlTree.insert(i); System.out.println("插入"+i+"后整颗树的高 " + avlTree.height()); } System.out.println("一般二叉查找树的先序遍历:"); searchTree.printTree(); System.out.println(); System.out.println("AVL树的先序遍历:"); avlTree.printTree(); }
根据这个遍历可以画出这个二叉树:
一般的二叉树就不用画了,根节点为0 ,一路向右走到底。超级不平衡!参考《数据结构与算法分析java版》remove方法参考http://blog.csdn.net/liyong199012/article/details/29219261
相关文章推荐
- AVL树的旋转图解和简单实现
- 平衡二叉树(AVL树)的简单实现
- iOS 强制屏幕实现旋转功能,超级简单。
- Android基础-简单的动画实现 *补间动画 *透明度渐变 *旋转动画 *位移动画 *如果需要几个效果整合到一起,需要将所有效果全部放到set里
- 图解wordpress3.5.1最简单实现伪静态的方法
- OpenGL绘制简单场景,实现旋转缩放平移和灯光效果
- AVL树的JAVA实现及AVL树的旋转算法
- Eigen库实现简单的旋转、平移操作
- QML中PinchArea实现简单的缩放和旋转
- AVL树的旋转操作 图解 最详细
- C++模板实现二叉树(六 AVL树基础与旋转)
- 平衡二叉树(AVL树)的简单实现
- OpenGL 鼠标3D旋转简单实现
- AVL树的旋转操作 图解 最详细
- RotateAniamtion_实现Android简单动画旋转案例
- 关于css旋转动画效果的简单实现
- C++模板实现二叉树(六 AVL树基础与旋转)
- AVL树的旋转实现
- 实现了简单的旋转动画
- android按钮旋转简单实现