⭐上篇文章:34.二叉树进阶3(C++STL 关联式容器,set/map的介绍与使用)-CSDN博客
⭐本篇代码:c++学习/19.map和set的使用用与模拟 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)
⭐标⭐是比较重要的部分
一. 二叉搜索树的缺点
之前文章中提到,普通的二叉搜索树在某些情况下会退出成链表,或者根节点的左右子树的高度差非常大。这个时候就会导致其搜索效率由 O(logN) -> O(N)。
为了解决这个问题,计算机科学家提供了AVL树。 AVL树一种平衡二叉搜索树,通过平衡因子和旋转操作来保证二叉搜索树是平衡的。
二. AVL树及其特点
1 AVL树是一颗二叉搜索树,满足二叉搜索树的性质
2 AVL树每一个节点中有一个平衡因子,表示该节点左右子树的高度差的绝对值,且该值不能超过1(即左右子树高度差不能超过1)
可以看到,通过平衡因子可以保证AVL树是高度平衡的!AVL树在每一次插入新节点之后都要检验该节点和其父节点的平衡因子,一旦检测到平衡因子的值超过1,就要通过旋转操作来调整平衡因子。
三. AVL树的节点和旋转操作图解
3.1 AVL树节点
cpp
//节点
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;//节点的双亲,用于更新平衡因子
int _bf;//平衡因子 balance factor
pair<K, V> _kv;
//构造函数
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
, _kv(kv)
{}
};
二叉树一般都是存储键值对<k,v>。AVL树需要一个平衡因子bf
3.2 AVL树结构
cpp
template<class K, class V>
class AVlLTree
{
typedef AVLTreeNode<K, V> Node;
public:
bool Insert(const pair<K, V>& kv)
{}
private:
Node* _root;
};
3.3 AVL树的插入操作 ⭐
AVL树的插入操作和二叉搜索树几乎一样,不过在插入节点之后要根据平衡因子才调整整棵树的结构。
a 左单旋
如果一个节点的平衡因子为2,即右子树比左子树高2则需要对该节点进行左单旋

代码如下:
cpp
//右右,左单旋.一共需要调整6条线,四个节点
void RotateLeft(Node* parent)
{
if (!parent)
return;
Node* ppNode = parent->_parent;//要旋转节点的父亲
Node* SubR = parent->_right;//要旋转节点的右孩子
Node* SubRLeft = SubR->_left;//要旋转节点右孩子的左孩子
//一:调整节点
parent->_right = SubRLeft;
if (SubRLeft)
SubRLeft->_parent = parent;
SubR->_left = parent;
parent->_parent = SubR;
//1.parent是根,现在SubR是根
//2.parent是整棵树的子树,找到其父亲,旋转完成后,让subR与其父亲相连接
if (_root == parent)
{
_root = SubR;
SubR->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = SubR;
}
else
{
ppNode->_right = SubR;
}
SubR->_parent = ppNode;
}
//二:更新平衡因子
parent->_bf = SubR->_bf = 0;
}
b 右单旋
如果一个节点的左子树比右子树高2,则需要对该节点进行右单旋

代码如下:
cpp
//左左,右单旋
void RotateRight(Node* parent)
{
if (!parent)
return;
Node* ppNode = parent->_parent;
Node* SubL = parent->_left;
Node* SubLRight = SubL->_right;
//1.旋转,调整节点
parent->_left = SubLRight;
if (SubLRight)
SubLRight->_parent = parent;
SubL->_right = parent;
parent->_parent = SubL;
if (_root == parent)
{
_root = SubL;
SubL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = SubL;
}
else
{
ppNode->_right = SubL;
}
SubL->_parent = ppNode;
}
//2.更新平衡因子
//旋转之后,subL为子树根,左子树高度为h+1,右子树为h+parent高度1.左右子树高度一样,平衡因子为0
//parent左右子树高度也相等,平衡因子为0
parent->_bf = SubL->_bf = 0;
}
c 左双旋
如果新增节点在较高右子树的左侧,则需要两次旋转。如下图

双旋可以复用单旋的代码
cpp
//右左
void RotateRightLeft(Node* parent)
{
//注意控制三个节点的平衡因子
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
int bf = SubRL->_bf;
RotateRight(parent->_right); //上图旋转10
RotateLeft(parent); //上图旋转5
//对应图来理解平衡因子
if (bf == -1)//右左节点的左边插入
{
parent->_bf = 0;
SubR->_bf = 1;
}
else if (bf == 1)//右左节点的右边插入
{
SubR->_bf = 0;
parent->_bf = -1;
}
else if (bf == 0)//此时SubRL就是新增节点
{
parent->_bf = 0;
SubR->_bf = 0;
}
//此时右左节点是根节点
SubRL->_bf = 0;
}
c 右双旋
如果新增节点在较高左子树的右侧,则需要两次旋转。如下图

cpp
//左右,先左旋parent->left,再右旋parent
void RotateLeftRight(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
int bf = SubLR->_bf;
RotateLeft(SubL); //上图旋转5
RotateRight(parent); //上图旋转10
//更新平衡因子
if (bf == 1)
{
parent->_bf = 0;
SubL->_bf = -1;
}
else if (bf == -1)
{
SubL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 0)
{
SubL->_bf = 0;
parent->_bf = 0;
}
//左右节点为根节点
SubLR->_bf = 0;
}
注意,每一次旋转之后都要更新平衡因子
四. 二叉树插入完整代码
cpp
#pragma once
#include<iostream>
#include<queue>
#include<string>
using namespace std;
//AVL树 (高度平衡二叉搜索树)
//1.是二叉搜索树
//2.树及所有子树都要满足左右左右子树的高度差不超过1
//为了方便实现,我们在这里引入了平衡因子的概念,其值范围只能是0,1,-1(平衡因子不是必须的)
//平衡因子=右子树的高度-左子树的高度
//这样就能够控制其高度位O(logN)
//节点
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;//节点的双亲,用于更新平衡因子
int _bf;//平衡因子 balance factor
pair<K, V> _kv;
//构造函数
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
, _kv(kv)
{}
};
//AVLTree
//1.按照搜索树的规则插入
//2.更新平衡因子
//3.如果没有出现违规的平衡因子,插入结束
//4.如果有存在违规的平衡因子,需要旋转
template<class K, class V>
class AVlLTree
{
typedef AVLTreeNode<K, V> Node;
public:
//一个节点的插入,只会影响其祖先的平衡因子,所以只要判断其祖先的平衡因子即可
//1.如果cur是其父亲的左,parent->_bf--.如果cur是其父亲的右,parent->_bf++
//2.在这些祖先中,更新完,parent->_bf==0,说明parent的高度不变,更新结束,插入完成(把这颗parent为根的子树矮的填上了)
//3.更新完parent,parent->_bf==1 or -1,parent高度变了,继续往上更新(说明更新前parent->_bf==0,parent的高度变了)\
//4.如果更新完parent的bf,parent->_bf==2, or -2,parent的所在子树不平衡,需要旋转处理,旋转后插入结束
bool Insert(const pair<K, V>& kv)
{
//1.先按搜索树进行插入
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
return false;
}
}
cur = new Node(kv);
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
cur->_parent = parent;//要将cur与其父亲链接
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//2.从插入节点开始对这个节点及所有祖先节点更新平衡因子
while (parent)
{
if (parent->_right == cur)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 0)
{
//parent所在子树高度不变,更新结束
break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
//parent所在子树高度变了,有0变1或者-1,此时继续向上更新
cur = parent;
parent = parent->_parent;
}
else //if (parent->_bf == 2 || parent->_bf == -2)
{
//parent所在子树出现问题,需要旋转处理
//1.旋转完成后,它还得是完整的搜索树
//2.旋转完成后,它要变平衡
//旋转方法
if (parent->_bf == 2)//左旋
{
if (cur->_bf == 1)//右右,直接对parent左单旋
{
//1.左单旋
//将subR的左边给parent的右边(parent的右边指向subR的左边)
//将parent变为subR的左边(subR的左边指向parent)
RotateLeft(parent);
}
else if (cur->_bf == -1)//右左,先对parent->right右单旋,再对parent左单旋
{
//左双旋
//先右单旋cur,让parent变为右右
//再左单旋parent
RotateRightLeft(parent);
}
}
else if (parent->_bf == -2)
{
if (cur->_bf == -1)//左左,直接对parent右单旋
{
//右单旋
RotateRight(parent);
}
else if (cur->_bf == 1)//左右,对parent->left左单旋,再对parent右单旋
{
//右双旋
//先左单旋cur,让parenttt变为左左
//再右单旋parent
//此时parent左右这个节点为根
RotateLeftRight(parent);
}
}
//旋转完成后之间跳出即可,这是由于旋转让我平衡了,高度恢复到了插入新节点之前的高度(即高度不会变化)
//如果是子树,对上层节点不会有影响。更新结束,跳出即可
break;
}
}
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
//右右,左单旋.一共需要调整6条线,四个节点
void RotateLeft(Node* parent)
{
if (!parent)
return;
Node* ppNode = parent->_parent;//要旋转节点的父亲
Node* SubR = parent->_right;//要旋转节点的右孩子
Node* SubRLeft = SubR->_left;//要旋转节点右孩子的左孩子
//一:调整节点
parent->_right = SubRLeft;
if (SubRLeft)
SubRLeft->_parent = parent;
SubR->_left = parent;
parent->_parent = SubR;
//1.parent是根,现在SubR是根
//2.parent是整棵树的子树,找到其父亲,旋转完成后,让subR与其父亲相连接
if (_root == parent)
{
_root = SubR;
SubR->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = SubR;
}
else
{
ppNode->_right = SubR;
}
SubR->_parent = ppNode;
}
//二:更新平衡因子
parent->_bf = SubR->_bf = 0;
}
//左左,右单旋
void RotateRight(Node* parent)
{
if (!parent)
return;
Node* ppNode = parent->_parent;
Node* SubL = parent->_left;
Node* SubLRight = SubL->_right;
//1.旋转,调整节点
parent->_left = SubLRight;
if (SubLRight)
SubLRight->_parent = parent;
SubL->_right = parent;
parent->_parent = SubL;
if (_root == parent)
{
_root = SubL;
SubL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = SubL;
}
else
{
ppNode->_right = SubL;
}
SubL->_parent = ppNode;
}
//2.更新平衡因子
//旋转之后,subL为子树根,左子树高度为h+1,右子树为h+parent高度1.左右子树高度一样,平衡因子为0
//parent左右子树高度也相等,平衡因子为0
parent->_bf = SubL->_bf = 0;
}
//右左
void RotateRightLeft(Node* parent)
{
//注意控制三个节点的平衡因子
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
int bf = SubRL->_bf;
RotateRight(parent->_right);
RotateLeft(parent);
//对应图来理解平衡因子
if (bf == -1)//右左节点的左边插入
{
parent->_bf = 0;
SubR->_bf = 1;
}
else if (bf == 1)//右左节点的右边插入
{
SubR->_bf = 0;
parent->_bf = -1;
}
else if (bf == 0)//此时SubRL就是新增节点
{
parent->_bf = 0;
SubR->_bf = 0;
}
//此时右左节点是根节点
SubRL->_bf = 0;
}
//左右,先左旋parent->left,再右旋parent
void RotateLeftRight(Node* parent)
{
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
int bf = SubLR->_bf;
RotateLeft(SubL);
RotateRight(parent);
if (bf == 1)
{
parent->_bf = 0;
SubL->_bf = -1;
}
else if (bf == -1)
{
SubL->_bf = 0;
parent->_bf = 1;
}
else if (bf == 0)
{
SubL->_bf = 0;
parent->_bf = 0;
}
//左右节点为根节点
SubLR->_bf = 0;
}
//中序遍历辅助函数
void _InOrder(Node* root)
{
if (!root)
return;
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << " 平衡因子:" << root->_bf << endl;
_InOrder(root->_right);
}
Node* _root;
};