详解什么是平衡二叉树

详解什么是平衡二叉树(AVL)(修订补充版)
前言
Wiki:在计算机科学中,AVL树是最早被发明的自平衡二叉查找树。在AVL树中,任一节点对应的两棵子树的最大高度差为1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是 O(logn)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。AVL 树得名于它的发明者 G. M. Adelson-Velsky 和 Evgenii Landis,他们在1962年的论文《An algorithm for the organization of information》中公开了这一数据结构。

1 为什么要有平衡二叉树
二叉搜索树一定程度上可以提高搜索效率,但是当原序列有序时,例如序列 A = {1,2,3,4,5,6},构造二叉搜索树如图 1.1。依据此序列构造的二叉搜索树为右斜树,同时二叉树退化成单链表,搜索效率降低为 O(n)。

图 1.1图 1.1

在此二叉搜索树中查找元素 6 需要查找 6 次。

二叉搜索树的查找效率取决于树的高度,因此保持树的高度最小,即可保证树的查找效率。同样的序列 A,将其改为图 1.2 的方式存储,查找元素 6 时只需比较 3 次,查找效率提升一倍。

图 1.2图 1.2

可以看出当节点数目一定,保持树的左右两端保持平衡,树的查找效率最高。

这种左右子树的高度相差不超过 1 的树为平衡二叉树。

  1. 定义
    平衡二叉查找树:简称平衡二叉树。由前苏联的数学家 Adelse-Velskil 和 Landis 在 1962 年提出的高度平衡的二叉树,根据科学家的英文名也称为 AVL 树。它具有如下几个性质:

可以是空树。
假如不是空树,任何一个节点的左子树与右子树都是平衡二叉树,并且高度之差的绝对值不超过 1。
平衡之意,如天平,即两边的分量大约相同。

例如图 2.1 不是平衡二叉树,因为节点 60 的左子树不是平衡二叉树。

图 2.1图 2.1

图 2.2 也不是平衡二叉树,因为虽然任何一个节点的左子树与右子树都是平衡二叉树,但高度之差已经超过 1 。

图 2.2图 2.2

图 2.3 是平衡二叉树。

图 2.3图 2.3

  1. 平衡因子
    定义:某节点的左子树与右子树的高度(深度)差即为该节点的平衡因子(BF,Balance Factor),平衡二叉树中不存在平衡因子大于 1 的节点。在一棵平衡二叉树中,节点的平衡因子只能取 0 、1 或者 -1 ,分别对应着左右子树等高,左子树比较高,右子树比较高。

图 3.1图 3.1 图 3.2图 3.2 图 3.3图 3.3

  1. 节点结构
    定义平衡二叉树的节点结构:

typedef struct AVLNode *Tree;

typedef int ElementType;

struct AVLNode{

int depth; //深度,这里计算每个节点的深度,通过深度的比较可得出是否平衡

Tree parent; //该节点的父节点

ElementType val; //节点值

Tree lchild;

Tree rchild;

AVLNode(int val=0) {
    parent = NULL;
    depth = 0;
    lchild = rchild = NULL;
    this->val=val;
}

};
5. AVL树插入时的失衡与调整
图 5.1 是一颗平衡二叉树

图 5.1 图 5.1

在此平衡二叉树插入节点 99 ,树结构变为:

动图 5.2动图 5.2

在动图 5.2 中,节点 66 的左子树高度为 1,右子树高度为 3,此时平衡因子为 -2,树失去平衡。

在动图 5.2 中,以节点 66 为父节点的那颗树就称为 最小失衡子树。

最小失衡子树:在新插入的节点向上查找,以第一个平衡因子的绝对值超过 1 的节点为根的子树称为最小不平衡子树。也就是说,一棵失衡的树,是有可能有多棵子树同时失衡的。而这个时候,我们只要调整最小的不平衡子树,就能够将不平衡的树调整为平衡的树。

平衡二叉树的失衡调整主要是通过旋转最小失衡子树来实现的。根据旋转的方向有两种处理方式,左旋 与 右旋 。

旋转的目的就是减少高度,通过降低整棵树的高度来平衡。哪边的树高,就把那边的树向上旋转。

5.1 左旋
图 5.1.1图 5.1.1

以图 5.1.1 为例,加入新节点 99 后, 节点 66 的左子树高度为 1,右子树高度为 3,此时平衡因子为 -2。为保证树的平衡,此时需要对节点 66 做出旋转,因为右子树高度高于左子树,对节点进行左旋操作,流程如下:

(1)节点的右孩子替代此节点位置
(2)右孩子的左子树变为该节点的右子树
(3)节点本身变为右孩子的左子树

整个操作流程如动图 5.1.2 所示。

动图 5.1.2动图 5.1.2

节点的右孩子替代此节点位置 —— 节点 66 的右孩子是节点 77 ,将节点 77 代替节点 66 的位置
右孩子的左子树变为该节点的右子树 —— 节点 77 的左子树为节点 75,将节点 75 挪到节点 66 的右子树位置
节点本身变为右孩子的左子树 —— 节点 66 变为了节点 77 的左子树
5.2 右旋
右旋操作与左旋类似,操作流程为:

(1)节点的左孩子代表此节点
(2)节点的左孩子的右子树变为节点的左子树
(3)将此节点作为左孩子节点的右子树。

动图 5.2.1动图 5.2.1

  1. AVL树的四种插入节点方式
    假设一颗 AVL 树的某个节点为 A,有四种操作会使 A 的左右子树高度差大于 1,从而破坏了原有 AVL 树的平衡性。平衡二叉树插入节点的情况分为以下四种:

图 6.0图 6.0

具体分析如下:

6.1 A的左孩子的左子树插入节点(LL)
只需要执行一次右旋即可。

动图 6.1动图 6.1

实现代码如下:

//LL型调整函数
//返回:新父节点
Tree LL_rotate(Tree node){
//node为离操作节点最近的失衡的节点
Tree parent=NULL,son;
//获取失衡节点的父节点
parent=node->parent;
//获取失衡节点的左孩子
son=node->lchild;
//设置son节点右孩子的父指针
if (son->rchild!=NULL) son->rchild->parent=node;
//失衡节点的左孩子变更为son的右孩子
node->lchild=son->rchild;
//更新失衡节点的高度信息
update_depth(node);
//失衡节点变成son的右孩子
son->rchild=node;
//设置son的父节点为原失衡节点的父节点
son->parent=parent;
//如果失衡节点不是根节点,则开始更新父节点
if (parent!=NULL){
//如果父节点的左孩子是失衡节点,指向现在更新后的新孩子son
if (parent->lchild==node){
parent->lchild=son;
}else{
//父节点的右孩子是失衡节点
parent->rchild=son;
}
}
//设置失衡节点的父亲
node->parent=son;
//更新son节点的高度信息
update_depth(son);
return son;

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值