二叉排序树的最坏情况
二叉排序树,依据左子树的值都小于根节点,右子树的值都大于根节点,如果一组数据是降序的有序序列,这样就会创建出一个左斜树,其实就成为了一个单链表。
退化为单链表之后,查询的时间复杂度就降低到O(n),这就是最坏时间复杂度。针对这种情况该如何解决呢,聪明的人想到了一种使二叉排序树不单偏的方法,通过平衡二叉树使二叉树左右两边的高度不至于差距太大。
AVL树
AVL树是计算机科学中最早被发明的自平衡二叉查找树。AVL树得名于它的发明者 格奥尔吉·阿杰尔松-韦利斯基(Georgii Adelson-Velskii)和叶夫根尼·兰迪斯(Yevgeny Mikhaylovich Landis),分别取他们名字的首字母组合而来,这一数据结构是在1962年被公开。
特性
- 如果一棵二叉树是AVL树,那它的根节点的左右子树也是AVL树,且左子树高度减右子树高度的绝对值小于等于1
- 对树的各种操作的时间复杂度始终保持在O(logn)级别
根据上面的特性,可得出AVL树的平衡因子是 height(leftsubtree) - height(rightsubtree)
节点
由于AVL树的平衡因子是需要计算左右子树高度的差值,因此我们需要在原二叉树节点数据结构的基础之上增加高度(height)变量。
typedef struct BAVLTreeNode {
int m_Data;
int m_Height;
BAVLTreeNode *m_LeftChild;
BAVLTreeNode *m_RightChild;
explicit BAVLTreeNode(int value):m_Data(value), m_Height(0), m_LeftChild(nullptr), m_RightChild(nullptr) {}
} BAVLTreeNode, *BAVLNode;
基本要素
节点的高度获取和节点高度差值计算是创建及调整一棵AVL的关键,下面就先完成这两块的代码。
节点高度
int BAVLTree::Height(BAVLNode node)
{
if (node == nullptr)
{
// https://zh.wikipedia.org/wiki/%E4%BA%8C%E5%8F%89%E6%A0%91
// 对于节点的高度,维基百科上是以边为计算依据,所以空节点的高度为-1,叶节点的高度为0
// 而leetcode和一些算法书上是以节点为计算依据,所以空节点的高度是0,叶节点的高度是1
// 这里就取了leetcode和一些算法书上的计算标准
return 0;
}
int height = node->m_Height;
return height;
}
void BAVLTree::UpdateHeight(BAVLNode node)
{
int height = std::max(Height(node->m_LeftChild), Height(node->m_RightChild)) + 1;
node->m_Height = height;
}
高度差值
高度差值就是平衡因子的计算。
int BAVLTree::BalanceFactor(BAVLNode node)
{
if (node == nullptr)
{
return 0;
}
// 平衡因子等于左子树高度减右子树高度
return Height(node->m_LeftChild) - Height(node->m_RightChild);
}
插入节点
AVL树是二叉排序树的一种变形,所以在插入节点的基础部分上和二叉排序树是一样,都需要先跟已有的节点进行大小比较,小的放左边,大的放右边。在这些操作完成之后,就需要进行自平衡操作,显示递归更新所有节点的高度,然后进行整棵树的平衡调整。
BAVLTree::BAVLNode BAVLTree::InsertNode(BAVLNode node, int value)
{
if (node == nullptr)
{
node = new BAVLTreeNode(value);
return node;
}
if (node->m_Data > value)
{
node->m_LeftChild = InsertNode(node->m_LeftChild, value);
}
else if (node->m_Data < value) {
node->m_RightChild = InsertNode(node->m_RightChild, value);
}
else
{
return node;
}
// 更新节点高度
UpdateHeight(node);
// 调整失衡节点及其子树
node = AdjustBalance(node);
return node;
}
调整树的平衡的依据是平衡因子,根据平衡因子计算的结果来确定当前失衡的节点是处于什么情况。
失衡节点
如何找到失衡节点?
从插入节点开始向上寻找,如果平衡因子的绝对值大于1,则是失衡节点。
-
LL和RR型,这两种类型刚好是镜像对称的。
- 从新插节点开始向上查找,找到失衡节点之后,从失衡节点开始向下查找两个节点,如果这两个节点都位于同一侧,就判定是左左型或右右型。
- LL型是左偏树,其实就是左侧太高导致整棵树向一侧偏斜,为了避免二叉排序树由于一侧太高而出现退化为链表的情况,就需要找到左侧子树的失衡节点,然后以失衡节点为基点向右旋转,这样就能使整棵树达到AVL树的要求。
- RR型是右偏树,旋转原理同LL型,但它需要向左旋转。
-
LR和RL型
- LR型也是左侧子树高于右侧子树,但同LL型不同的是它是失衡节点的左侧节点下面只有右侧节点,这样在调整时,只得先调整下部分即节点2和节点4,先以节点2为基点向左旋转,旋转完成之后就是一棵LL型的失衡树,再按照LL型的旋转逻辑向右旋转就行。
- RL型是右侧子树高于左侧子树,其调整同LR型,但由于是右边偏斜,所以需要先调整节点7和节点6,而且是向右旋转,使其形成一棵RR型失衡树,然后再向左旋转调整。
失衡类型 | 旋转方式 |
---|---|
LL型 | 右旋 |
RR型 | 左旋 |
LR型 | 先左旋再右旋 |
RL型 | 先右旋再左旋 |
旋转
左旋
BAVLTree::BAVLNode BAVLTree::LeftRotate(BAVLNode node)
{
// ●
// ●
// ●
BAVLNode rChild = node->m_RightChild;
BAVLNode rLeftChild = rChild->m_LeftChild;
rChild->m_LeftChild = node;
node->m_RightChild = rLeftChild;
UpdateHeight(node);
UpdateHeight(rChild);
return rChild;
}
右旋
BAVLTree::BAVLNode BAVLTree::RightRotate(BAVLNode node)
{
// ●
// ●
// ●
BAVLNode lChild = node->m_LeftChild;
BAVLNode lRightNode = lChild->m_RightChild;
lChild->m_RightChild = node;
node->m_LeftChild = lRightNode;
UpdateHeight(node);
UpdateHeight(lChild);
return lChild;
}
调整
BAVLTree::BAVLNode BAVLTree::AdjustBalance(BAVLNode node)
{
int factor = BalanceFactor(node);
if (factor > 1) //左子树高度大于右子树高度,形成左偏树
{
int leftFactor = BalanceFactor(node->m_LeftChild);
//再找节点的左子节点的平衡因子
// ● --- 该节点的右侧没有右子节点,所以平衡因子是2
// ● --- 该节点如果有左子节点,无论是否有右子节点,都确定从该节点的父节点开始,是一棵左左型子树
// ● ●
if (leftFactor >= 0)//LL型
{
return RightRotate(node);
}
// ●
// ● --- 该节点只有右子节点,所以平衡因子是-1,形成LR型子树
// ●
else //LR 型
{
// 以失衡节点的左子树为基点进行调整
node->m_LeftChild = LeftRotate(node->m_LeftChild);
return RightRotate(node);
}
}
else if (factor < -1)// 右子树高度小于左子树高度,形成右偏树
{
int rightFactor = BalanceFactor(node->m_RightChild);
// ● --- 该节点的右侧没有左子节点,所以平衡因子是2
// ● --- 该节点如果有右子节点,无论是否有左子节点,都确定从该节点的父节点开始,是一棵右右型子树
// ● ●
if (rightFactor <= 0)//RR型
{
return LeftRotate(node);
}
// ●
// ● --- 该节点只有左子节点,所以平衡因子是-1,形成RL型子树
// ●
else //RL型
{
// 以失衡节点的右子节点为基点进行调整
node->m_RightChild = RightRotate(node->m_RightChild);
return LeftRotate(node);
}
}
return node;
}
删除节点
AVL树删除节点需要分两步,一是按照二叉排序树的方式进行节点删除,二是在删除节点之后,需要递归遍历整棵树查找失衡节点并通过调整使其恢复平衡。
删除
BAVLTree::BAVLNode BAVLTree::DeleteNode(BAVLNode node, int value)
{
if (node == nullptr)
{
return nullptr;
}
if (node->m_Data > value)
{
node->m_LeftChild = DeleteNode(node->m_LeftChild, value);
}
else if (node->m_Data < value)
{
node->m_RightChild = DeleteNode(node->m_RightChild, value);
}
else {
// 1、没有子树,
// 2、仅有左子树
// 3、仅有右子树
if (node->m_LeftChild == nullptr || node->m_RightChild == nullptr)
{
BAVLNode child = node->m_LeftChild != nullptr?node->m_LeftChild:node->m_RightChild;
if (child == nullptr)
{
delete node;
return nullptr;
}
else {
delete node;
node = child;
}
}
else // 有左右子树,这里就需要用 二叉排序树按中序遍历 得到的 前驱或后继的值 来替换当前被删除的节点,然后删除前驱或后继节点
{
// 这次使用后继节点来替换当前被删除的节点
// 先找右子树的根节点
BAVLNode tmpNode = node->m_RightChild;
// 以tmpNode为基点再向左查找最终的左节点
while (tmpNode->m_LeftChild)
{
tmpNode = tmpNode->m_LeftChild;
}
int tmpValue = tmpNode->m_Data;
// 删除要替换被删除节点的节点
node->m_RightChild = DeleteNode(node->m_RightChild, tmpNode->m_Data);
node->m_Data = tmpValue;
}
}
// 更新节点高度
UpdateHeight(node);
// 调整失衡的节点
node = AdjustBalance(node);
return node;
}
查找节点
由于AVL树也是一棵二叉排序树,查找节点也是一样的方式,这里就不再赘述。
测试代码
int main() {
std::vector<int> vArr = {40, 36, 23, 14, 90, 44, 32, 78, 10, 9, 3, 20};
BAVLTree *bTree = new BAVLTree();
BAVLTree::BAVLTreeNode *root;
std::vector<int>::iterator cbegin = vArr.begin();
std::vector<int>::iterator cend = vArr.end();
while(cbegin != cend)
{
root = bTree->InsertNode(root, *cbegin);
cbegin++;
}
bTree->InorderTraversal(root);
std::cout << std::endl;
root = bTree->DeleteNode(root, 14);
bTree->InorderTraversal(root);
std::cout << std::endl;
delete bTree;
while (true)
{
/* code */
}
}
//输出结果:
3 9 10 14 20 23 32 36 40 44 78 90
3 9 10 20 23 32 36 40 44 78 90