目录
1.简介
2.定义
3.基础操作
1.左旋
2.右旋
4.平衡操作
1.LL
2.RL
3.RR
5.泛型声明
6.树的结构
7.插入
1.左旋代码
2.右旋代码
3.平衡代码
4.共用代码
5.插入代码
8.删除
9.遍历
1.简介
上篇:Unity C#二叉排序树的原理及泛型写法详解(图文)
介绍了二叉排序树的原理及写法,发现树的形状是不可控的:好的情况下它近似于完全二叉树O(logn),坏的情况下它是一颗斜树O(n);如果它是一颗斜树时,查询的时间复杂度和线性表就没有差别了。所以有没有使二叉排序树在插入时变得近似于一颗完全二叉树的写法?
当然是有的,这里我们介绍平衡(AVL)二叉树,下文简称AVL树
AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差绝对值为1,所以它也被称为高度平衡树,它名字来源于它的发明作者G.M. Adelson-Velsky 和 E.M. Landis。
2.定义
AVL树中任何节点的两个子树的高度最大差绝对值为1,所以当在插入的过程中某个节点的两个子树高度差的绝对值大于1,则要进行平衡操作(后文详解)来让它符合AVL树的特征。我们将节点的左子树高度 - 右子树高度的差值命名为平衡因子BalanceFactor,简称bf。
如下图,即为标准的AVL树
我们再来看,下图是AVL树吗?
不是, 为什么?见下图各节点的bf因子,节点(97)的bf = -2,绝对值大于1,根据定义不符合AVL树定义,所以它不是
再来看下图呢?
也不是,因为有个节点的bf = -2;
那这张呢?
它是,因为所有节点的bf绝对值都不大于1
通过如上各图我们已经清楚了什么是AVL树,那当bf因子的绝对值大于1时,要怎么处理呢?
这里会对数进行平衡操作,以符合AVL树的定义
下面我们先引出平衡的两个基础操作:左旋、右旋
3.基础操作
1.左旋
左旋即节点沿着逆时针方向变换,将当前节点(67)变为它的原右子树(80)的左子树,并将原右子树(80)的左子树(72)变为节点(67)的右子树,见下图:
2.右旋
右旋即节点沿着顺时针方向变换,将当前节点(80)变为它的原左子树(67)的右子树,并将原左子树(67)的右子树(72)变为节点(80)的左子树,见上图:
4.平衡操作
先定义如下枚举:
public enum AVLRotateMode
{
LL, // 左旋
LR, // 先左旋(左子树),再右旋
RR, // 右旋
RL, // 先右旋(右子树),再左旋
}
如上,对于平衡操作我们有四种类型:
1.LL
当节点(67)的bf绝对值大于1,且bf小于0时。看它的右子节点(72)的bf符号,和它(67)相同时,则进行左旋,标记为LL
2.RL
当节点(82)的bf绝对值大于1,且bf小于0时。看它的右子节点(91)的bf符号,和它(82)不相同时,则要先对节点(82)的右子节点(91)做右旋,然后再对节点(82)做左旋,标记为RL
3.RR
当节点(67)的bf绝对值大于1,且bf大于0时。看它的左子节点(55)的bf符号,和它(67)相同时,则进行右旋,标记为RR
当节点(67)的bf绝对值大于1,且bf大于0时。看它的左子节点(36)的bf符号,和它(67)不相同时,则要先对节点(67)的左子节点(36)做左旋,然后再对节点(67)做右旋,标记为LR
有了以上概念,我们来看树的结构及常用操作:插入、删除、遍历
5.泛型声明
泛型数据需实现IComparerSearch接口,GetCompareValue()返回值用于比较计算,如下:
// -------------------------------------------------------------------------------------
// 常用搜索算法(泛型)(仅支持int比较:GetCompareValue())
// 泛型要实现IComparerSearch接口 示例如下:
//public class Data : IComparerSearch
//{
// public int id; // 示例字段,可自定义
//
// public int GetCompareValue()
// {
// return this.id;
// }
//}
// -------------------------------------------------------------------------------------
/// <summary>
/// 泛型查找需要实现此接口
/// </summary>
public interface IComparerSearch
{
/// <summary>
/// 获取参与排序比较的值
/// </summary>
int GetCompareValue();
}
6.树的结构
/// <summary>
/// 平衡(AVL)二叉树泛型数据结构
/// </summary>
public class AVLTree<T> where T : IComparerSearch
{
public T value;
public AVLTree<T> lChild;
public AVLTree<T> rChild;
public int height; // 节点的高度,用于平衡因子bf的计算(节点左子树的高度 - 节点右子树的高度),bf 的绝对值小于等于1为平衡状态
}
7.插入
同二叉排序树的插入,区别是在插入后对节点做一次平衡调整
用到的相关接口如下:
1.左旋代码
/// <summary>
/// 平衡二叉树节点左旋转(逆时针)
/// </summary>
private static void LeftRotate<T>(ref AVLTree<T> node) where T : IComparerSearch
{
AVLTree<T> newNode = node.rChild;
node.rChild = newNode.lChild;
newNode.lChild = node;
node = newNode;
node.lChild.height = CalculateTreeHeight(node.lChild);
node.height = CalculateTreeHeight(node);
}
2.右旋代码
/// <summary>
/// 平衡二叉树节点右旋转(顺时针)
/// </summary>
private static void RightRotate<T>(ref AVLTree<T> node) where T : IComparerSearch
{
AVLTree<T> newNode = node.lChild;
node.lChild = newNode.rChild;
newNode.rChild = node;
node = newNode;
node.rChild.height = CalculateTreeHeight(node.rChild);
node.height = CalculateTreeHeight(node);
}
3.平衡代码
/// <summary>
/// 对平衡(AVL)二叉树做平衡调整
/// </summary>
private static void BalanceAVLTree<T>(ref AVLTree<T> root) where T : IComparerSearch
{
// 计算树的高度
root.height = CalculateTreeHeight(root);
// 计算平衡因子bf(节点左子树的深度 - 节点右子树的深度),bf 的绝对值小于等于1为平衡状态
int bf = GetAVLTreeBalanceFactor(root);
if (bf < -1 || bf > 1)
{
// 进行树的平衡
AVLRotateMode mode = AVLRotateMode.LL;
int bfChild = 0;
if (bf < -1)
{
bfChild = GetAVLTreeBalanceFactor(root.rChild);
mode = bfChild <= 0 ? AVLRotateMode.LL : AVLRotateMode.RL;
}
else
{
bfChild = GetAVLTreeBalanceFactor(root.lChild);
mode = bfChild >= 0 ? AVLRotateMode.RR : AVLRotateMode.LR;
}
switch (mode)
{
case AVLRotateMode.LL:
// 左旋
LeftRotate(ref root);
break;
case AVLRotateMode.LR:
// 先左旋(左子树),再右旋
LeftRotate(ref root.lChild);
RightRotate(ref root);
break;
case AVLRotateMode.RR:
// 右旋
RightRotate(ref root);
break;
case AVLRotateMode.RL:
// 先右旋(右子树),再左旋
RightRotate(ref root.rChild);
LeftRotate(ref root);
break;
}
}
}
4.共用代码
/// <summary>
/// 获取平衡二叉树某节点的平衡因子bf
/// </summary>
private static int GetAVLTreeBalanceFactor<T>(AVLTree<T> node) where T : IComparerSearch
{
return node != null ? GetAVLTreeHeight(node.lChild) - GetAVLTreeHeight(node.rChild) : 0;
}
/// <summary>
/// 计算平衡二叉树某节点的高度
/// </summary>
private static int CalculateTreeHeight<T>(AVLTree<T> node) where T : IComparerSearch
{
return Max(GetAVLTreeHeight(node.lChild), GetAVLTreeHeight(node.rChild)) + 1;
}
/// <summary>
/// 获取平衡二叉树某节点的高度
/// </summary>
private static int GetAVLTreeHeight<T>(AVLTree<T> node) where T : IComparerSearch
{
return node != null ? node.height : 0;
}
private static int Max(int x, int y)
{
return x > y ? x : y;
}
5.插入代码
/// <summary>
/// 平衡二叉树的插入(相同的key.GetCompareValue()值不会重复插入)
/// </summary>
public static bool InsertAVLTree<T>(T key, ref AVLTree<T> root) where T : IComparerSearch
{
if (root == null)
{
root = new AVLTree<T>();
root.value = key;
root.height = 1;
root.lChild = null;
root.rChild = null;
return true;
}
else if (root.value.GetCompareValue() == key.GetCompareValue())
{
// 树中已存在相同的key,不能重复插入
return false;
}
else if (root.value.GetCompareValue() > key.GetCompareValue())
{
// 插入左子树
InsertAVLTree(key, ref root.lChild);
}
else
{
// 插入右子树
InsertAVLTree(key, ref root.rChild);
}
// 对二叉树做平衡调整
BalanceAVLTree(ref root);
return true;
}
8.删除
删除操作基本和二叉排序树的删除相同,差别在于在删除后对节点做一次平衡调整
/// <summary>
/// 平衡(AVL)二叉树移除一个元素
/// </summary>
// <typeparam name="T">实现IComparerSearch的泛型数据</typeparam>
// <param name="key">查找的值(同IComparerSearch的GetCompareValue()返回值相比较)</param>
// <param name="root">平衡二叉树的节点</param>
public static bool RemoveAVLTree<T>(int key, ref AVLTree<T> root) where T : IComparerSearch
{
if (root == null)
// 没有找到要移除的元素
return false;
// 找到需要移除的节点
if (root.value.GetCompareValue() == key)
{
if (root.lChild != null && root.rChild != null)
{
// 左右子树都不为空(移除key对应的节点数据,并取左子树最大的节点作为新的节点数据替换)
AVLTree<T> coverNode = root.lChild;
if (coverNode.rChild == null)
{
// 左子树没有右子节点,则直接把左子树替换到移除位置
coverNode.rChild = root.rChild;
root = coverNode;
}
else
{
// 取左子树最大的节点作为新的节点数据替换
AVLTree<T> coverParent = root;
while (coverNode.rChild != null)
{
coverParent = coverNode;
coverNode = coverNode.rChild;
}
if (coverNode.lChild != null)
coverParent.rChild = coverNode.lChild;
else
coverParent.rChild = null;
coverNode.lChild = root.lChild;
coverNode.rChild = root.rChild;
root = coverNode;
// 对调整后的左子树做平衡调整
BalanceAVLTree(ref root.lChild);
}
}
else if (root.lChild != null)
{
// 左子树不为空
root = root.lChild;
}
else if (root.rChild != null)
{
// 右子树不为空
root = root.rChild;
}
else
{
// 左右子树均为空,移除当前节点即可
root = null;
}
}
else if (key < root.value.GetCompareValue())
{
// 左子树查找
RemoveAVLTree(key, ref root.lChild);
}
else
{
// 右子树查找
RemoveAVLTree(key, ref root.rChild);
}
// 对二叉树做平衡调整
if (root != null)
BalanceAVLTree(ref root);
return true;
}
这里有如下一句要注意:
// 对调整后的左子树做平衡调整
BalanceAVLTree(ref root.lChild);
这句不能漏,见下图:
当节点(49)被删除后,会被节点(44)所替换,这时节点(44)的左子节点的bf绝对值超过1,需要进行平衡调整 。所以这句是对特殊情况的平衡处理
9.遍历
同二叉排序树的遍历
/// <summary>
/// 遍历(中序)平衡二叉树并返回一个有序list(默认升序)
/// </summary>
/// <param name="root">平衡二叉树的根节点</param>
/// <param name="isUp">true:升序;false:降序</param>
public static List<T> QueryAVLTree<T>(AVLTree<T> root, bool isUp = true) where T : IComparerSearch
{
List<T> list = new List<T>();
QueryAVLTree(root, isUp, ref list);
return list;
}
private static void QueryAVLTree<T>(AVLTree<T> node, bool isUp, ref List<T> list) where T : IComparerSearch
{
if (node == null)
return;
if (isUp)
{
// 中序遍历(升序)
QueryAVLTree(node.lChild, isUp, ref list);
list.Add(node.value);
QueryAVLTree(node.rChild, isUp, ref list);
}
else
{
// 降序
QueryAVLTree(node.rChild, isUp, ref list);
list.Add(node.value);
QueryAVLTree(node.lChild, isUp, ref list);
}
}