目录
- 一.介绍
- 二.简单实现AVL树
- 1. 基本框架
- 2. 插入结点(Insert)
- a. 更新平衡因子
- b. 左单旋
- c. 右单旋
- d. 左右双旋
- e. 右左双旋
- 3. 删除节点(Erase)
- a. 更新平衡因子
- b. 旋转
- c. 代码
- 4. 测试
一.介绍
作为对二叉搜索树的优化版本。AVL树是由俄罗斯的两位数学家G.M.Adelson-Velskii和E.M.Landis发明的,并以名字的首字母命名。
AVL树(可以为空树),在二叉搜索树的基础上具有以下性质:
-
它的左右子树都是AVL树
-
左右子树高度差(简称平衡因子)的绝对值不超过1(-1/0/1)
平衡因子 = 右子树高度 - 左子树高度
示例三图,都符合AVL树的规则:在二叉搜索树的继承上,每个节点的平衡因子都是-1、0、1
平衡因子 = 右子树高度 - 左子树高度
有N个结点的AVL树,,其高度 H ≈ l o g 2 N H \approx log_2N H≈log2N,则其查找的时间复杂度可以控制在 O ( l o g 2 N ) O(log_2N) O(log2N)
二.简单实现AVL树
1. 基本框架
template<class K>
struct AVLTreeNode
{
AVLTreeNode<K>* _left;
AVLTreeNode<K>* _right;
AVLTreeNode<K>* _parent;
K _key;
int _bf; // balance factor
AVLTreeNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _key(key)
, _bf(0) //新节点平衡因子为0
{}
};
template<class K>
struct AVLTree
{
typedef AVLTreeNode<K> Node;
private:
Node* _root = nullptr;
};
_root:指向AVL树的根节点
使用三叉链的结构(_parent),是为了后续的操作。
2. 插入结点(Insert)
由于AVL树的节点插入,可能会引起子树高度的变化,从而改变平衡因子,导致其不再符合AVL树的规则。
步骤:
- 按照搜索二叉树进行插入新节点
- 更新AVL树节点的平衡因子,当不再平衡时,进行旋转调整平衡
a. 更新平衡因子
平衡因子更新规则:
子树的高度改变,则需要更新其父节点的平衡因子;
直到更新到根节点或左右子树平衡(_bf=0)或者违反规则( _bf=2/-2)停止。
-
新节点在父节点的左边,父节点的平衡因子–
-
新节点在父节点的右边,父节点的平衡因子++
节点 _bf(平衡因子)更改后的操作:
平衡因子_bf | 操作 | 解析 |
---|---|---|
1或-1 | 向其父节点更新_bf | 子树的高度改变 |
0 | 停止更新 | 左右子树高度相等(平衡) |
2或-2 | 停止更新,继续旋转 | 违反规则,需要进行调整 |
bool Insert(const K& key)
{
// 插入第一个节点
if (nullptr == _root)
{
_root = new Node(key);
return true;
}
// 插入新节点
Node* cur = _root;
Node* parent = nullptr;
//寻找新节点位置(即cur到空时)
while (cur)
{
parent = cur;
if (cur->_key > key) // 在左子树
{
cur = cur->_left;
}
else if (cur->_key < key) // 在右子树
{
cur = cur->_right;
}
else // 已存在,退出
{
return false;
}
}
cur = new Node(key);
if (parent->_key < key)//新节点的值大于parent
parent->_right = cur;//放在parent的右边
else
parent->_left = cur;//左边
cur->_parent = parent;
//更新平衡因子
while (cur != _root) // 最坏情况下更新到根节点
{
if (parent->_left == cur) //cur在parent左边,平衡因子--
parent->_bf--;
else //cur在parent右边,平衡因子++
parent->_bf++;
if (parent->_bf == 0) // 左右平衡,停止更新
{
break;
}
else if (parent->_bf == 1 || parent->_bf == -1) // 继续往上更新
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)//违反规则
{
if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent); //左旋
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent); //右旋
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent); //右左双旋
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent); //左右双旋
}
break;
}
else
{
assert(false);//出现问题,终止程序
}
}
return true;
}
旋转的情况详情在下面
对于节点的 _bf=2或-2 时,旋转的情况大致分为以下4种
b. 左单旋
构成左单旋的条件是:parent=2 && subR=1
左旋:即节点11与节点22的结构 逆时针(向左)旋转了一下
步骤:
- 让subRL成为parent的右子树
- 将parent作为subR的左子树
- subR成为根(子树/树)
- 更新parent与subR的平衡因子
void RotateL(Node* parent)
{
Node* pParent = parent->_parent;//parent的父节点
Node* subR = parent->_right;//parent的右孩子
Node* subRL = subR->_left;//subR的左孩子
//让subRL成为parent的右子树
parent->_right = subRL;
if (subRL)//如果subRL不为空,更改其父指针
{
subRL->_parent = parent;
}
//将parent作为subR的左子树
subR->_left = parent;
parent->_parent = subR;
//subR成为根(树/子树)
if (_root == parent)//parent为根节点
{
_root = subR;
_root->_parent = nullptr;
}
else //parent为一棵子树
{
//将subR链接到pParent上
if (pParent->_right == parent)
pParent->_right = subR;
else
pParent->_left = subR;
subR->_parent = pParent;
}
// 更新parent与subR的平衡因子
parent->_bf = subR->_bf = 0;
}
c. 右单旋
产生左单旋的条件是:parent=-2 && subL=-1
右旋:即节点23与节点20的结构 顺时针(向右)旋转了一下
步骤:
- 让subLR成为parent的左子树
- 将parent作为subL的右子树
- subL成为根(子树/树)
- 更新parent与subL的平衡因子
void RotateR(Node* parent)
{
Node* pParent = parent->_parent;//parent的父节点
Node* subL = parent->_left;//parent的左孩子
Node* subLR = subL->_right;//subL的右孩子
//让subLR成为parent的左子树
parent->_left = subLR;
if (subLR)//如果subLR不为空,更改其父指针
{
subLR->_parent = parent;
}
// 将parent作为subL的右子树
subL->_right = parent;
parent->_parent = subL;
// subL成为根(子树/树)
if (parent == _root)//parent为根节点
{
_root = subL;
_root->_parent = nullptr;
}
else //parent为一棵子树
{
//将subR链接到pParent上
if (pParent->_right == parent)
pParent->_right = subL;
else
pParent->_left = subL;
subL->_parent = pParent;
}
// 更新parent与subL的平衡因子
subL->_bf = parent->_bf = 0;
}
d. 左右双旋
由图左右双旋分为三种情况
可以发现插入位置不同,subLR->_bf=-1/1/0不同,导致右左旋转后平衡因子不同。
步骤:
- 将subL为根的子树左旋
- 将parent为根的子树右旋
- 更新parent、subL和subLR的平衡因子
void RotateLR(Node* parent)
{
Node* subL = parent->_left;//parent的左孩子
Node* subLR = subL->_right;//subL的右孩子
int bf = subLR->_bf; // 旋转前subLR的平衡因子
RotateL(subL);//左旋
RotateR(parent);//右旋
// 更新平衡因子
if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
subLR->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
subLR->_bf = 0;
}
else
assert(false); //错误
}
e. 右左双旋
由图右左双旋分为三种情况,触发条件为parent=2 && subR=-1
可以发现插入位置不同,subRL->_bf=-1/1/0不同,导致右左旋转后平衡因子不同。
步骤:
- 将subL为根的子树左旋
- 将parent为根的子树右旋
- 更新parent、subR和subRL的平衡因子
void RotateRL(Node* parent)
{
Node* subR = parent->_right;//parent的右孩子
Node* subRL = subR->_left;//subR的左孩子
int bf = subRL->_bf; //旋转前subRL的平衡因子
RotateR(subR);//右旋
RotateL(parent);//左旋
// 更新三种情况的平衡因子
if (bf == -1)
{
parent->_bf = 0;
subR->_bf = 1;
subRL->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
subR->_bf = 0;
subRL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
subRL->_bf = 0;
}
else
assert(false);//错误
}
3. 删除节点(Erase)
和插入类似,删除节也可能会引起子树高度的变化,从而改变平衡因子,导致其不再符合AVL树的规则。
步骤:
- 按照搜索二叉的操作删除节点
- 更新AVL树节点的平衡因子,当不再平衡时,进行旋转调整平衡
a. 更新平衡因子
平衡因子更新规则:
子树的高度改变,则需要更新其父节点的平衡因子;
直到更新到根节点或左右子树平衡(_bf=0)或者违反规则( _bf=2/-2)停止。
- 删除节点在父节点的左边,父节点的平衡因子++
- 删除节点在父节点的右边,父节点的平衡因子–
节点 _bf(平衡因子)更改后的操作:
平衡因子_bf | 操作 | 解析 |
---|---|---|
1或-1 | 停止更新 | 删除前_bf=0,其左右子树高度H相等。删除后,该节点为根的子树高度为H+1不变 |
0 | 向其父节点更新_bf | 删除前_bf=-1/1,左子树高或右子树高。删除后左右子树高度一样,即该节点为根的子树高度变小(减1) |
2或-2 | 停止更新,继续旋转 | 违反规则,需要进行调整 |
b. 旋转
对于节点的 _bf=2或-2 时,旋转的情况大致分为以下6种
1)parent的_bf为 2 , subR的 _bf为 1----》左旋
2)parent的_bf为 -2 , subL的 _bf为 -1----》右旋
3)parent的_bf为 2 , subR的 _bf为 0----》左旋
4)parent的_bf为 -2 , subL的 _bf为 0----》右旋
5)parent的_bf为 -2 , subL的 _bf为 -1----》左右双旋
6)parent的_bf为 2 , subR的 _bf为 -1----》右左双旋
3)左单旋后,需更新平衡因子parent=1、subR=-1
4)右单旋后,需更新平衡因子parent=-1、subL=1
除3)和4)外,其他4种旋转和插入的旋转相同。3)和4)旋转后新子树根的_bf=-1/1,停止更新;但是其他4种情况中,旋转后新子树根的 _bf=0,因此旋转后任需向上更新。
c. 代码
bool Erase(const K& key)
{
if (_root == nullptr)// 空树,退出
return false;
Node* cur = _root;//当前节点
Node* parent = nullptr;//当前节点的父节点
Node* del = nullptr; // 要删除的节点
Node* pDel = nullptr; // 要删除节点的父节点
//查找要删除的点
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else // 找到
{
if (cur->_left == nullptr) //所删除节点的左子树为空
{
if (cur == _root) //如果是删除根节点
{
_root = cur->_right;
if (_root)//如果cur->_right不为空
_root->_parent = nullptr;
delete cur;
return true;
}
else
{
// 记录要删除的节点和其父节点
del = cur;
pDel = parent;
}
}
else if (cur->_right == nullptr) // 待删除节点的右子树为空
{
if (cur == _root) //如果是删除根节点
{
_root = cur->_left;
if (_root)//如果cur->_left不为空
_root->_parent = nullptr;
delete cur;
return true;
}
else
{
// 记录要删除的节点和其父节点
del = cur;
pDel = parent;
}
}
else //左右都不为空
{
// 找到待删除节点右子树中的最左节点进行替换删除
Node* minRight = cur->_right;//右子树的最小值节点
Node* minParent = cur;//minRight的父节点
while (minRight->_left)//即右子树的最左节点
{
minParent = minRight;
minRight = minRight->_left;
}
// 交换值
std::swap(cur->_key, minRight->_key);
// 记录要删除的节点和其父节点
del = minRight;
pDel = minParent;
}
break;
}
}
// 遍历结束,没有找到待删除节点
if (cur == nullptr)
return false;
//更新平衡因子
cur = del;
parent = pDel;
while (cur != _root)// 最坏情况下更新到根节点
{
if (cur == parent->_left)//cur在parent左边,平衡因子++
{
parent->_bf++;
}
else if (cur == parent->_right)//cur在parent右边,平衡因子--
{
parent->_bf--;
}
if (parent->_bf == 0)//继续向上更新
{
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
break;//停止更新
}
else if (parent->_bf == 2 || parent->_bf == -2)//旋转
{
if (parent->_bf == 2 && parent->_right->_bf == 1)
{
Node* tmp = parent->_right; //记录左单旋转后的根节点
RotateL(parent);//左单旋
parent = tmp; // 更新parent
}
else if (parent->_bf == -2 && parent->_left->_bf == -1)
{
Node* tmp = parent->_left;//记录右单旋转后的根节点
RotateR(parent);//右单旋
parent = tmp;
}
else if (parent->_bf == 2 && parent->_right->_bf == 0)
{
Node* tmp = parent->_right; //记录左单旋转后的根节点
RotateL(parent);//左单旋
parent = tmp;
//更新平衡因子
parent->_bf = -1;
parent->_left->_bf = 1;
break;//停止更新
}
else if (parent->_bf == -2 && parent->_left->_bf == 0)
{
Node* tmp = parent->_left;//记录右单旋转后的根节点
RotateR(parent);//右单旋
parent = tmp;
//更新平衡因子
parent->_bf = 1;
parent->_right->_bf = -1;
break;//停止更新
}
else if (parent->_bf == -2 && parent->_left->_bf == 1)
{
Node* tmp = parent->_left->_right;//记录左右双旋转后的根节点
RotateLR(parent);//左右双旋
parent = tmp;
}
else if (parent->_bf == 2 && parent->_right->_bf == -1)
{
Node* tmp = parent->_right->_left;//记录右左双旋转后的根节点
RotateRL(parent);//右左双旋转
parent = tmp;
}
else
{
assert(false);// 出现错误
}
//继续向上更新
cur = parent;
parent = parent->_parent;
}
}
if (del->_left == nullptr)//所删除节点的左子树为空
{
//将cur的右子树链接到父节点
if (del == pDel->_left)
{
pDel->_left = del->_right;
if (del->_right)
del->_right->_parent = pDel;
}
else
{
pDel->_right = del->_right;
if (del->_right)
del->_right->_parent = pDel;
}
}
else// 待删除节点的右子树为空
{
//将cur的左子树链接到父节点
if (del == pDel->_left)
{
pDel->_left = del->_left;
if (del->_left)
del->_left->_parent = pDel;
}
else
{
pDel->_right = del->_left;
if (del->_left)
del->_left->_parent = pDel;
}
}
delete del;// 删除节点
return true;
}
4. 测试
为了验证上述的插入(Insert)、删除(Erase)函数编写的正确性,可以检查操作后的AVL树是否还符合规则。
//是否平衡yes:1,no:0
bool IsBalance()
{
return _IsBalance(_root);
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;//空树是AVL树
}
int leftHT = Height(root->_left);
int rightHT = Height(root->_right);
//平衡因子=右子树高度-左子树高度
int bf = rightHT - leftHT;
if (bf != root->_bf)//如果不同,则报错
{
cout << root->_key << "平衡因子异常" << endl;
return false;
}
return abs(bf) < 2 //平衡因子的绝对值小于2
&& _IsBalance(root->_left) //左右子树也为AVL树
&& _IsBalance(root->_right);
}
int Height(Node* root)//求树高
{
if (root == nullptr)
return 0;
return max(Height(root->_left), Height(root->_right)) + 1;
}
示例:
void TestAVLTree3()
{
size_t N = 10000;
srand(time(0));
AVLTree<int> t1;
//插入N次随机数
int count = 0;
for (size_t i = 0; i < N; ++i)
{
int x = rand();
if(t1.Insert(x)) ++count;
}
cout << "插入成功次数:" << count << endl;
//删除N次随机数
count = 0;
for (int i = 0; i < N; ++i)
{
int x = rand();
if (t1.Erase(x)) ++count;
}
cout << "删除成功次数:" << count << endl;
//是否仍是AVL树
cout << "IsBalance:" << t1.IsBalance() << endl;
}
int main()
{
TestAVLTree();
return 0;
}
🦀🦀观看~