二叉平衡树是二叉树中最为最要的概念之一,也是在语言库或者项目中应用比较广泛的一种特殊的树形结构。

二叉平衡树

AVL树是高度平衡的而二叉树。它的特点是:AVL树中任何节点的两个子树的高度最大差别为1。 通常AVL树是在二叉搜索树上,经过局部调整而建立的,因此,它还是一棵排序树。

上面的两张图片,左边的是AVL树,它的任何节点的两个子树的高度差别都<=1;而右边的不是AVL树,因为7的两颗子树的高度相差为2(以2为根节点的树的高度是3,而以8为根节点的树的高度是1)。

性质

左右子树相差不超过1.


建立

public class AVLTree<T extends Comparable<T>> {private AVLTreeNode<T> mRoot;    // 根结点// AVL树的节点(内部类)class AVLTreeNode<T extends Comparable<T>> {T key;                // 关键字(键值)int height;         // 高度AVLTreeNode<T> left;    // 左孩子AVLTreeNode<T> right;    // 右孩子public AVLTreeNode(T key, AVLTreeNode<T> left, AVLTreeNode<T> right) {this.key = key;this.left = left;this.right = right;this.height = 0;}}
}

旋转

如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡。这种失去平衡的可以概括为4种姿态:LL(左左),LR(左右),RR(右右)和RL(右左)。

而列举的关于”失去平衡的AVL树”的例子。总的来说,AVL树失去平衡时的情况一定是LL、LR、RL、RR这4种之一,它们都由各自的定义:

(1) LL:LeftLeft,也称为”左左”。插入或删除一个节点后,根节点的左子树的左子树还有非空子节点,导致”根的左子树的高度”比”根的右子树的高度”大2,导致AVL树失去了平衡。
例如,在上面LL情况中,由于”根节点(8)的左子树(4)的左子树(2)还有非空子节点”,而”根节点(8)的右子树(12)没有子节点”;导致”根节点(8)的左子树(4)高度”比”根节点(8)的右子树(12)”高2。

(2) LR:LeftRight,也称为”左右”。插入或删除一个节点后,根节点的左子树的右子树还有非空子节点,导致”根的左子树的高度”比”根的右子树的高度”大2,导致AVL树失去了平衡。
例如,在上面LR情况中,由于”根节点(8)的左子树(4)的左子树(6)还有非空子节点”,而”根节点(8)的右子树(12)没有子节点”;导致”根节点(8)的左子树(4)高度”比”根节点(8)的右子树(12)”高2。

(3) RL:RightLeft,称为”右左”。插入或删除一个节点后,根节点的右子树的左子树还有非空子节点,导致”根的右子树的高度”比”根的左子树的高度”大2,导致AVL树失去了平衡。
例如,在上面RL情况中,由于”根节点(8)的右子树(12)的左子树(10)还有非空子节点”,而”根节点(8)的左子树(4)没有子节点”;导致”根节点(8)的右子树(12)高度”比”根节点(8)的左子树(4)”高2。

(4) RR:RightRight,称为”右右”。插入或删除一个节点后,根节点的右子树的右子树还有非空子节点,导致”根的右子树的高度”比”根的左子树的高度”大2,导致AVL树失去了平衡。
例如,在上面RR情况中,由于”根节点(8)的右子树(12)的右子树(14)还有非空子节点”,而”根节点(8)的左子树(4)没有子节点”;导致”根节点(8)的右子树(12)高度”比”根节点(8)的左子树(4)”高2。

如果在AVL树中进行插入或删除节点后,可能导致AVL树失去平衡。AVL失去平衡之后,可以通过旋转使其恢复平衡,下面分别介绍”LL(左左),LR(左右),RR(右右)和RL(右左)”这4种情况对应的旋转方法。

LL的旋转

LL失去平衡的情况,可以通过一次旋转让AVL树恢复平衡。如下图:

图中左边是旋转之前的树,右边是旋转之后的树。从中可以发现,旋转之后的树又变成了AVL树,而且该旋转只需要一次即可完成。

对于LL旋转,你可以这样理解为:LL旋转是围绕”失去平衡的AVL根节点”进行的,也就是节点k2;而且由于是LL情况,即左左情况,就用手抓着”左孩子,即k1”使劲摇。将k1变成根节点,k2变成k1的右子树,”k1的右子树”变成”k2的左子树”。

/** LL:左左对应的情况(左单旋转)。** 返回值:旋转后的根节点*/
private AVLTreeNode<T> leftLeftRotation(AVLTreeNode<T> k2) {AVLTreeNode<T> k1;k1 = k2.left;k2.left = k1.right;k1.right = k2;k2.height = max( height(k2.left), height(k2.right)) + 1;k1.height = max( height(k1.left), k2.height) + 1;return k1;
}

RR的旋转

理解了LL之后,RR就相当容易理解了。RR是与LL对称的情况!RR恢复平衡的旋转方法如下:

图中左边是旋转之前的树,右边是旋转之后的树。RR旋转也只需要一次即可完成。

/** RR:右右对应的情况(右单旋转)。** 返回值:旋转后的根节点*/
private AVLTreeNode<T> rightRightRotation(AVLTreeNode<T> k1) {AVLTreeNode<T> k2;k2 = k1.right;k1.right = k2.left;k2.left = k1;k1.height = max( height(k1.left), height(k1.right)) + 1;k2.height = max( height(k2.right), k1.height) + 1;return k2;
}

LR的旋转

LR失去平衡情况需要经过两次旋转才能达到平衡。

第一次旋转是围绕”k1”进行的”RR旋转”,第二次是围绕”k3”进行的”LL旋转”。

/** LR:左右对应的情况(左双旋转)。** 返回值:旋转后的根节点*/
private AVLTreeNode<T> leftRightRotation(AVLTreeNode<T> k3) {k3.left = rightRightRotation(k3.left);//右右旋转return leftLeftRotation(k3);//左左旋转
}

RL的旋转

RL的旋转与上面LR的旋转是对称的,也是需要两次操作才能平衡。

/** RL:右左对应的情况(右双旋转)。** 返回值:旋转后的根节点*/
private AVLTreeNode<T> rightLeftRotation(AVLTreeNode<T> k1) {k1.right = leftLeftRotation(k1.right);//先左左return rightRightRotation(k1);//再右右
}

插入

插入过程与二叉搜索树的插入总体过程是相同的,但是,在插入完成以后,必须要判断整棵树是不是处于平衡的状态,如果不平衡的话,要根据上面四种情况来进行调整。具体代码,见文章尾;


删除

删除过程与二叉搜索树的删除总体过程是相同的,删除节点后,先调整树,使其有序,然后,必须要判断整棵树是不是处于平衡的状态,如果不平衡的话,要根据上面四种情况来进行调整。具体代码,见文章尾;

/*** Java 语言: AVL树** @author skywang* @date 2013/11/07*/public class AVLTree<T extends Comparable<T>> {private AVLTreeNode<T> mRoot;    // 根结点// AVL树的节点(内部类)class AVLTreeNode<T extends Comparable<T>> {T key;                // 关键字(键值)int height;         // 高度AVLTreeNode<T> left;    // 左孩子AVLTreeNode<T> right;    // 右孩子public AVLTreeNode(T key, AVLTreeNode<T> left, AVLTreeNode<T> right) {this.key = key;this.left = left;this.right = right;this.height = 0;}}// 构造函数public AVLTree() {mRoot = null;}/** 获取树的高度*/private int height(AVLTreeNode<T> tree) {if (tree != null)return tree.height;return 0;}public int height() {return height(mRoot);}/** 比较两个值的大小*/private int max(int a, int b) {return a>b ? a : b;}/** 前序遍历"AVL树"*/private void preOrder(AVLTreeNode<T> tree) {if(tree != null) {System.out.print(tree.key+" ");preOrder(tree.left);preOrder(tree.right);}}public void preOrder() {preOrder(mRoot);}/** 中序遍历"AVL树"*/private void inOrder(AVLTreeNode<T> tree) {if(tree != null){inOrder(tree.left);System.out.print(tree.key+" ");inOrder(tree.right);}}public void inOrder() {inOrder(mRoot);}/** 后序遍历"AVL树"*/private void postOrder(AVLTreeNode<T> tree) {if(tree != null) {postOrder(tree.left);postOrder(tree.right);System.out.print(tree.key+" ");}}public void postOrder() {postOrder(mRoot);}/** (递归实现)查找"AVL树x"中键值为key的节点*/private AVLTreeNode<T> search(AVLTreeNode<T> x, T key) {if (x==null)return x;int cmp = key.compareTo(x.key);if (cmp < 0)return search(x.left, key);else if (cmp > 0)return search(x.right, key);elsereturn x;}public AVLTreeNode<T> search(T key) {return search(mRoot, key);}/** (非递归实现)查找"AVL树x"中键值为key的节点*/private AVLTreeNode<T> iterativeSearch(AVLTreeNode<T> x, T key) {while (x!=null) {int cmp = key.compareTo(x.key);if (cmp < 0)x = x.left;else if (cmp > 0)x = x.right;elsereturn x;}return x;}public AVLTreeNode<T> iterativeSearch(T key) {return iterativeSearch(mRoot, key);}/* * 查找最小结点:返回tree为根结点的AVL树的最小结点。*/private AVLTreeNode<T> minimum(AVLTreeNode<T> tree) {if (tree == null)return null;while(tree.left != null)tree = tree.left;return tree;}public T minimum() {AVLTreeNode<T> p = minimum(mRoot);if (p != null)return p.key;return null;}/* * 查找最大结点:返回tree为根结点的AVL树的最大结点。*/private AVLTreeNode<T> maximum(AVLTreeNode<T> tree) {if (tree == null)return null;while(tree.right != null)tree = tree.right;return tree;}public T maximum() {AVLTreeNode<T> p = maximum(mRoot);if (p != null)return p.key;return null;}/** LL:左左对应的情况(左单旋转)。** 返回值:旋转后的根节点*/private AVLTreeNode<T> leftLeftRotation(AVLTreeNode<T> k2) {AVLTreeNode<T> k1;k1 = k2.left;k2.left = k1.right;k1.right = k2;k2.height = max( height(k2.left), height(k2.right)) + 1;k1.height = max( height(k1.left), k2.height) + 1;return k1;}/** RR:右右对应的情况(右单旋转)。** 返回值:旋转后的根节点*/private AVLTreeNode<T> rightRightRotation(AVLTreeNode<T> k1) {AVLTreeNode<T> k2;k2 = k1.right;k1.right = k2.left;k2.left = k1;k1.height = max( height(k1.left), height(k1.right)) + 1;k2.height = max( height(k2.right), k1.height) + 1;return k2;}/** LR:左右对应的情况(左双旋转)。** 返回值:旋转后的根节点*/private AVLTreeNode<T> leftRightRotation(AVLTreeNode<T> k3) {k3.left = rightRightRotation(k3.left);return leftLeftRotation(k3);}/** RL:右左对应的情况(右双旋转)。** 返回值:旋转后的根节点*/private AVLTreeNode<T> rightLeftRotation(AVLTreeNode<T> k1) {k1.right = leftLeftRotation(k1.right);return rightRightRotation(k1);}/* * 将结点插入到AVL树中,并返回根节点** 参数说明:*     tree AVL树的根结点*     key 插入的结点的键值* 返回值:*     根节点*/private AVLTreeNode<T> insert(AVLTreeNode<T> tree, T key) {if (tree == null) {// 新建节点tree = new AVLTreeNode<T>(key, null, null);if (tree==null) {System.out.println("ERROR: create avltree node failed!");return null;}} else {int cmp = key.compareTo(tree.key);if (cmp < 0) {    // 应该将key插入到"tree的左子树"的情况tree.left = insert(tree.left, key);// 插入节点后,若AVL树失去平衡,则进行相应的调节。if (height(tree.left) - height(tree.right) == 2) {if (key.compareTo(tree.left.key) < 0)tree = leftLeftRotation(tree);elsetree = leftRightRotation(tree);}} else if (cmp > 0) {    // 应该将key插入到"tree的右子树"的情况tree.right = insert(tree.right, key);// 插入节点后,若AVL树失去平衡,则进行相应的调节。if (height(tree.right) - height(tree.left) == 2) {if (key.compareTo(tree.right.key) > 0)tree = rightRightRotation(tree);elsetree = rightLeftRotation(tree);}} else {    // cmp==0System.out.println("添加失败:不允许添加相同的节点!");}}tree.height = max( height(tree.left), height(tree.right)) + 1;return tree;}public void insert(T key) {mRoot = insert(mRoot, key);}/* * 删除结点(z),返回根节点** 参数说明:*     tree AVL树的根结点*     z 待删除的结点* 返回值:*     根节点*/private AVLTreeNode<T> remove(AVLTreeNode<T> tree, AVLTreeNode<T> z) {// 根为空 或者 没有要删除的节点,直接返回null。if (tree==null || z==null)return null;int cmp = z.key.compareTo(tree.key);if (cmp < 0) {        // 待删除的节点在"tree的左子树"中tree.left = remove(tree.left, z);// 删除节点后,若AVL树失去平衡,则进行相应的调节。if (height(tree.right) - height(tree.left) == 2) {AVLTreeNode<T> r =  tree.right;if (height(r.left) > height(r.right))tree = rightLeftRotation(tree);elsetree = rightRightRotation(tree);}} else if (cmp > 0) {    // 待删除的节点在"tree的右子树"中tree.right = remove(tree.right, z);// 删除节点后,若AVL树失去平衡,则进行相应的调节。if (height(tree.left) - height(tree.right) == 2) {AVLTreeNode<T> l =  tree.left;if (height(l.right) > height(l.left))tree = leftRightRotation(tree);elsetree = leftLeftRotation(tree);}} else {    // tree是对应要删除的节点。// tree的左右孩子都非空if ((tree.left!=null) && (tree.right!=null)) {if (height(tree.left) > height(tree.right)) {// 如果tree的左子树比右子树高;// 则(01)找出tree的左子树中的最大节点//   (02)将该最大节点的值赋值给tree。//   (03)删除该最大节点。// 这类似于用"tree的左子树中最大节点"做"tree"的替身;// 采用这种方式的好处是:删除"tree的左子树中最大节点"之后,AVL树仍然是平衡的。AVLTreeNode<T> max = maximum(tree.left);tree.key = max.key;tree.left = remove(tree.left, max);} else {// 如果tree的左子树不比右子树高(即它们相等,或右子树比左子树高1)// 则(01)找出tree的右子树中的最小节点//   (02)将该最小节点的值赋值给tree。//   (03)删除该最小节点。// 这类似于用"tree的右子树中最小节点"做"tree"的替身;// 采用这种方式的好处是:删除"tree的右子树中最小节点"之后,AVL树仍然是平衡的。AVLTreeNode<T> min = maximum(tree.right);tree.key = min.key;tree.right = remove(tree.right, min);}} else {AVLTreeNode<T> tmp = tree;tree = (tree.left!=null) ? tree.left : tree.right;tmp = null;}}return tree;}public void remove(T key) {AVLTreeNode<T> z; if ((z = search(mRoot, key)) != null)mRoot = remove(mRoot, z);}/* * 销毁AVL树*/private void destroy(AVLTreeNode<T> tree) {if (tree==null)return ;if (tree.left != null)destroy(tree.left);if (tree.right != null)destroy(tree.right);tree = null;}public void destroy() {destroy(mRoot);}/** 打印"二叉查找树"** key        -- 节点的键值 * direction  --  0,表示该节点是根节点;*               -1,表示该节点是它的父结点的左孩子;*                1,表示该节点是它的父结点的右孩子。*/private void print(AVLTreeNode<T> tree, T key, int direction) {if(tree != null) {if(direction==0)    // tree是根节点System.out.printf("%2d is root\n", tree.key, key);else                // tree是分支节点System.out.printf("%2d is %2d's %6s child\n", tree.key, key, direction==1?"right" : "left");print(tree.left, tree.key, -1);print(tree.right,tree.key,  1);}}public void print() {if (mRoot != null)print(mRoot, mRoot.key, 0);}
}

代码参考自如果天空不死

二叉树--二叉平衡树相关推荐

  1. 数据结构-树和二叉树(六)二叉平衡树

    本文详细介绍了二叉平衡树的定义.平衡二叉树的插入调整以及查找效率分析! Let's go!

  2. 数据结构源码笔记(C语言):二叉平衡树的相关操作算法

    //二叉平衡树的相关运算 #include<stdio.h> #include<malloc.h> #include<string.h>typedef char I ...

  3. 数据结构与算法——二叉平衡树(AVL树)详解

    文章目录 AVL树概念 不平衡概况 四种平衡旋转方式 RR平衡旋转(左单旋转) LL平衡旋转(右单旋转) RL平衡旋转(先右后左双旋转) LR平衡旋转(先左后右单旋转) java代码实现 总结 AVL ...

  4. AVL树(二叉平衡树)详解与实现

    公众号文章链接 AVL树概念 前面学习二叉查找树和二叉树的各种遍历,但是其查找效率不稳定(斜树),而二叉平衡树的用途更多.查找相比稳定很多.(欢迎关注数据结构专栏) AVL树是带有平衡条件的二叉查找树 ...

  5. 【Python数据结构】——二叉平衡树AVL(查找、构建、删除、插入、打印、遍历)

    #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2021/7/28 20:57 # @Author : @linlianqin # @S ...

  6. 【数据结构进阶】二叉平衡树

    一. 二叉平衡树 概念 二叉搜索树有称 二叉排序树,它也可以是一个空树. 如果它的左子树不为空,则左子树上所有结点的值都小于根结点的值 如果他的右子树不为空,则右子树上所有结点的值都大于根结点的值 它 ...

  7. C语言实现二叉平衡树

    [前置知识]二叉树和二叉排序树 目录 1.什么是AVL树 2.怎么平衡 前言 对于大量的数据而言,链表的线性访问时间太慢,不宜使用.本章节将会介绍一种简单的数据结构:树(tree),其大部分操作的运行 ...

  8. 二叉平衡树(C++)

    背景: 二叉平衡树,就是根据二叉搜索树进行优化,让其速度更加的快,如果读者没有学过二叉搜索树,可以前往以下链接查看资料: http://t.csdn.cn/Sjjjshttp://t.csdn.cn/ ...

  9. 入门二叉平衡树的世界

    入门二叉平衡树的世界 1. 二叉平衡树的概念     二叉平衡树又称AVL树,它或者是一棵空二叉树,或者是具有下列性质的二叉树: 1) 根的左右子树高度之差的绝对值不超过1: 2) 根的左右子树都是二 ...

最新文章

  1. 2021入门推荐系统,应该从哪入手?
  2. 开源纯C日志函数库iLOG3快速入门(八、如果你喜欢简单日志函数甚于日志函数库)...
  3. 思科宣布NB-IoT平台实现商用
  4. 如何修改product base category
  5. JDK和CGLIB动态代理区别
  6. vb.net 功能f8键事件_憋了三年,史上最全的 F1~F12 键用法整理出来了!
  7. OJ1070: 小汽车的位置(C语言)
  8. 苹果电脑java如何降级,如何在Mac OSX上降级JRE / JDK?
  9. 如何查看某个查询用了多少TempDB空间
  10. SQL触发器 常用语句
  11. project euler 开坑
  12. LabVIEW基于Kvaser USBcan的UDS烧录Hex文件
  13. 服务器宕机是什么意思?服务器宕机原因及解决方法
  14. mysql audit log_关于MySQL AUDIT(审计)那点事
  15. 少女长期与宠物睡觉 遭“宠物虫”噬骨导致瘫痪
  16. 数据结构——二叉树的实现
  17. JAVA常见面试题(下)
  18. Android适配--dimen
  19. Java 执行 JS 脚本工具
  20. 电化学甲醛气体传感器

热门文章

  1. 维智创始人陶闯博士:利用数字孪生空间计算供需平衡寻找最优解|元宇宙与碳中和
  2. 梦幻西游python验证成语_python正则表达式抓取成语网站
  3. 【2021-04-25】APP逆向之某智网加密数据
  4. Apple 好莱坞艳照门的安全分析
  5. 中国古代四大名著电子书(android)
  6. 【本地ASP网站】Microsoft OLE DB Provider for ODBC Drivers
  7. 微信小程序之校园二手交易系统app毕业设计ssm
  8. 做自己想做的工作,你也可以
  9. ios, android平台手机游戏,《王者荣耀》ios和安卓怎么转平台 ios和安卓转平台攻略...
  10. 一款小程序软件开发报价包含哪些费用呢?