AVL树
- 引言
- AVL树的模拟实现
- AVL树的底层结构
- insert的实现
- 实现思路
- 旋转逻辑
- insert的完整代码
- insert的验证
- 源码
引言
前面 二叉搜索树
的时间复杂度那里提过一嘴 AVL树 和 红黑树
. 因为二叉搜索树的时间复杂度是 O(高度次), 最坏情况下 -- 退化到单支链, 是 O(N)
; AVL 和 红黑树
可以避免这种 极端情况, 时间复杂度是 O(log N)
🗨️AVL树是如何做到避免 二叉搜索树的极端情况的呢?
- 利用了
三叉链 && 平衡因子
三叉链 和 平衡因子是互相成就的,.
至于它们到底是什么, 有什么妙用, 下面会有详细的解释
AVL树的模拟实现
AVL树的底层结构
AVL树具有以下的特点:
- 左右子树都是AVL树
- 每棵子树的
高度差(平衡因子)
的绝对值不超过 1(-1, 0 1)
- 一般的平衡因子是 : 右子树的高度 - 左子树的高度
🗨️为啥不让每棵子树的 平衡因子的绝对值为 0 ?
AVL树的底层结构:
- AVLTreeNode类
template<class K, class V>
struct AVLTreeNode
{
public:
AVLTreeNode(const pair<K,V>& kv)
:_kv(kv)
{}
public:
pair<K, V> _kv;
AVLTreeNode<K, V>* _left = nullptr;
AVLTreeNode<K, V>* _right = nullptr;
AVLTreeNode<K, V>* _parent = nullptr;
int _bf = 0;
};
- AVLTree类
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
AVLTree()
:_root(nullptr)
{}
private:
// 根节点
Node* _root = nullptr;
// 记录旋转次数
int RotateCount = 0;
};
insert的实现
实现思路
二叉树的插入逻辑 + 更新平衡因子
bool Insert(const pair<K, V>& kv)
{
//二叉搜索树的插入逻辑
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = _root;
Node* cur = _root;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
// 链接
cur = new Node(kv);
if (cur->_kv.first > parent->_kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
parent = cur->_parent;
// 更新平衡因子
// ... ...
}
那重点就是 如何更新平衡因子
:
首先, 先明确; 新插入的节点不影响自己的平衡因子, 只会影响父亲节点到 root这一段的节点的平衡因子
其次, 要讨论插入节点的位置
- 新插在左 — — parent的平衡因子减减
- 新插在右 — — parent的平衡因子加加
最后, 也要讨论插入后的parent的平衡因子
- 更新后的parent的平衡因子为 1 或 -1 — — 继续往上更新
- 更新后的parent的平衡因子为 0 — — 停止更新
- 更新后的parent的平衡因子为 2 或 -2 — — 需要旋转 — — 旋转后停止更新
这里有几个问题:
🗨️为什么更新后的parent的平衡因子等于 1 或 -1, 要继续往上更新?
- 更新后的parent等于 0, 说明新插入的节点并不会影响parent的高度差
如果更新后的parent等于 1 或 -1, 说明新插入的节点影响到了parent的高度差, 有可能也会影响到parent上面的高度差 ⇒ 所以, 我们要继续向上更新, 直至parent的平衡因子为 0 或 更新到了root
🗨️ 为什么更新后的parent等于空, 也要停止更新呢?
- 首先,
只有root 的父亲节点才是 空
其次,我们最差的更新情况是更新到root节点
🗨️ 为什么旋转后就停止更新了呢?
- 结合后面的
旋转逻辑
来进行讲解
insert的主体结构👇👇👇
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = _root;
Node* cur = _root;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
// 链接
cur = new Node(kv);
if (cur->_kv.first > parent->_kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
parent = cur->_parent;
// 更新平衡因子
while (parent) // 最差更新到root节点
{
// 1. 先更新一下parent
// 新插在右
if (parent->_right == cur)
{
parent->_bf++;
}
else // 新插在左
{
parent->_bf--;
}
// 2. 检查更新后的parent是否影响平衡
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)
{
// 旋转的逻辑
// ...
// ...
// 旋转后停止更新平衡因子
break;
}
}
return true;
}
旋转逻辑
原本的树形结构符合AVL树的特点, 如果插入一个新节点 造成不平衡了, 即parent的平衡因子 等于 2 或 -2了, 这时候就要进行旋转. 根据 插入节点的位置
, 一共有四种情况:
- 新节点插入到较高左子树的左侧 — —
左左
— —对parent进行右旋
核心操作: 让cur的右节点充当parent的左节点, 然后让parent整体充当cur的右节点
- 原理 :
左边偏高 — —想办法让左边的高度降下来
— —把cur的左右高度差降低
每次旋转, 也要维持搜索树的特性
— —中序遍历是有序的
— — cur的右节点 (b
) 充当parent的左节点是合理的, parent充当 cur的右也是合理的
⇒ 这样以来:cur的左右子树的高度是相等的, 都为 h+1;
void RotateL(Node* parent)
{
// 每次旋转都++
++RotateCount;
// 提前保存grandfather节点, 保证后面的链接是正确的
Node* cur = parent->_right;
Node* grandfather = parent->_parent;
Node* curleft = cur->_left;
// 旋转核心
parent->_right = curleft;
cur->_left = parent;
// 更新父亲
// 1. parent && curleft
if (curleft)
{
curleft->_parent = parent;
}
parent->_parent = cur;
// 2.更新cur
// cur要充当起parent的责任, 向上进行连接
if (grandfather == nullptr)
{
cur->_parent = nullptr;
_root = cur;
}
else
{
// 判读cur应该位于grandfather节点的哪一侧
// 1. 向下进行链接
if (grandfather->_left == parent)
{
grandfather->_left = cur;
}
else
{
grandfather->_right = cur;
}
// 2. 向上进行链接
cur->_parent = grandfather;
}
// 更新平衡因子
cur->_bf = parent->_bf = 0;
}
- 新节点插入到较高右子树的右侧 — —
右右
void RotateR(Node* parent)
{
++RotateCount;
Node* cur = parent->_left;
Node* grandfather = parent->_parent;
Node* curright = cur->_right;
// 旋转核心
parent->_left = curright;
cur->_right = parent;
// 更新链接关系
// 1. parent && curright
if (curright)
{
curright->_parent = parent;
}
parent->_parent = cur;
// 2.更新cur
if (grandfather == nullptr)
{
cur->_parent = nullptr;
_root = cur;
}
else
{
if (grandfather->_left == parent)
{
grandfather->_left = cur;
}
else
{
grandfather->_right = cur;
}
cur->_parent = grandfather;
}
cur->_bf = parent->_bf = 0;
}
- 新节点插入到较高左子树的右侧 — —
左右
— —先对cur左旋, 再对parent右旋
- 其实
左右双旋的本质
是:
把curright的左子树(b
)充当cur的右子树,
把curright的右子树(c
)充当parent的左子树,
然后curright充当根节点, cur 和 parent分别充当左右子树
⇒ 那么 更新平衡因子, 也是要看curright的左右子树(b 和 c的高度情况)
:
- h = 0 ⇒ curright 为新增
cur->bf = 0;
curright->_bf = 0;
parent->_bf = 0;
- h > 0 ⇒ 新增在 b 或 c
(1)新增在 b
cur->bf = 0;
curright->_bf = 0;
parent->_bf = 1;
(2)新增在 c
cur->bf = -1;
curright->_bf = 0;
parent->_bf = 0;
左右双旋的完整代码
void RotateLR(Node* parent)
{
// 提前保存一份, 后面的左右旋转中会发生变化的
Node* cur = parent->_left;
Node* curright = cur->_right;
RotateL(parent->_left);
RotateR(parent);
// 更新平衡因子
if (curright->_bf == 0)
{
cur->_bf = 0;
parent->_bf = 0;
curright->_bf = 0;
}
else if (curright->_bf == 1)
{
cur->_bf = -1;
parent->_bf = 0;
curright->_bf = 0;
}
else if (curright->_bf == -1)
{
cur->_bf = 0;
parent->_bf = 1;
curright->_bf = 0;
}
}
- 新节点插入到较高右子树的左侧 — —
右左
— —先对cur右旋, 再对parent左旋
- 其实, 右左双旋的本质是:
把curleft的左子树(b
) 充当 parent的右子树
把curleft的右子树(c
) 充当cur的左子树
让curleft来根节点, 让parent 和 cur分别充当curleft的左右子树
⇒ 那么 更新平衡因子, 也是要看curleft的左右子树(b 和 c的高度情况)
:
- h = 0 ⇒ curleft为新增
cur->bf = 0;
curleft->_bf = 0;
parent->_bf = 1;
- h > 0 ⇒ 新增在b / c
(1) 新增在 b
cur->bf = 1;
curleft->_bf = 0;
parent->_bf = 0;
(2) 新增在 c
cur->bf = 0;
curleft->_bf = 0;
parent->_bf = -1;
右左双旋的完整代码
void RotateRL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
RotateR(parent->_right);
RotateL(parent);
// 更新平衡因子
if (curleft->_bf == 0)
{
cur->_bf = 0;
parent->_bf = 0;
curleft->_bf = 0;
}
else if (curleft->_bf == 1)
{
cur->_bf = 0;
parent->_bf = -1;
curleft->_bf = 0;
}
else if (curleft->_bf == -1)
{
cur->_bf = 1;
parent->_bf = 0;
curleft->_bf = 0;
}
}
insert的完整代码
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = _root;
Node* cur = _root;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
// 链接
cur = new Node(kv);
if (cur->_kv.first > parent->_kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
parent = cur->_parent;
// 更新平衡因子
while (parent)
{
// 1. 先更新一下parent
// 新插在右
if (parent->_right == cur)
{
parent->_bf++;
}
else // 新插在左
{
parent->_bf--;
}
// 2. 检查更新后的parent是否影响平衡
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)
{
RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
else
{
assert("平衡因子更新错误!");
}
// 旋转结束, 就停止更新平衡因子
break;
}
}
return true;
}
insert的验证
- 检查每棵子树的高度差的绝对值小于 1
- 检查平衡因子是否等于左右子树的高度差 (
我们算的平衡因子有可能不对
)
检查程序
int Height()
{
return Height(_root);
}
int Height(Node* root)
{
if (root == nullptr)
return 0;
int left = Height(root->_left);
int right = Height(root->_right);
return left > right ? left + 1 : right + 1;
}
bool Isbalance()
{
return Isbalance(_root);
}
bool Isbalance(Node* root)
{
if (root == nullptr)
return true;
int lheight = Height(root->_left);
int rheight = Height(root->_right);
if (root->_bf != rheight - lheight || abs(rheight - lheight) > 1)
{
cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
return false;
}
// 继续检查下一个支树
return Isbalance(root->_left) && Isbalance(root->_right);
}
GetRotateCount
int GetRoateCount()
{
return RotateCount;
}
验证代码:
void avl_test()
{
const int N = 100000;
vector<int> v;
v.reserve(N);
// srand((unsigned int)time(nullptr));
for (size_t i = 0; i < N; i++)
{
// int ret = rand();
// v.push_back(ret);
v.push_back(i);
}
muyu::AVLTree<int, int> avl;
for (auto e : v)
{
avl.Insert(make_pair(e, e));
}
cout << "AVL树是否达标-> " << avl.Isbalance() << endl;
cout << "AVL树的高度-> " << avl.Height() << endl;
cout << "AVL树旋转的次数-> " << avl.GetRoateCount() << endl;
}
int main()
{
avl_test();
return 0;
}
运行结果:
AVL树是否达标-> 1
AVL树的高度-> 17
AVL树旋转的次数-> 99983
源码
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace muyu
{
template<class K, class V>
struct AVLTreeNode
{
public:
AVLTreeNode(const pair<K,V>& kv)
:_kv(kv)
{}
public:
pair<K, V> _kv;
AVLTreeNode<K, V>* _left = nullptr;
AVLTreeNode<K, V>* _right = nullptr;
AVLTreeNode<K, V>* _parent = nullptr;
int _bf = 0;
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
AVLTree()
:_root(nullptr)
{}
void RotateL(Node* parent)
{
// 每次旋转都++
++RotateCount;
// 提前保存grandfather节点, 保证后面的链接是正确的
Node* cur = parent->_right;
Node* grandfather = parent->_parent;
Node* curleft = cur->_left;
// 旋转核心
parent->_right = curleft;
cur->_left = parent;
// 更新父亲
// 1. parent && curleft
if (curleft)
{
curleft->_parent = parent;
}
parent->_parent = cur;
// 2.更新cur
// cur要充当起parent的责任, 向上进行连接
if (grandfather == nullptr)
{
cur->_parent = nullptr;
_root = cur;
}
else
{
// 判读cur应该位于grandfather节点的哪一侧
// 1. 向下进行链接
if (grandfather->_left == parent)
{
grandfather->_left = cur;
}
else
{
grandfather->_right = cur;
}
// 2. 向上进行链接
cur->_parent = grandfather;
}
// 更新平衡因子
cur->_bf = parent->_bf = 0;
}
void RotateR(Node* parent)
{
++RotateCount;
Node* cur = parent->_left;
Node* grandfather = parent->_parent;
Node* curright = cur->_right;
// 旋转核心
parent->_left = curright;
cur->_right = parent;
// 更新链接关系
// 1. parent && curright
if (curright)
{
curright->_parent = parent;
}
parent->_parent = cur;
// 2.更新cur
if (grandfather == nullptr)
{
cur->_parent = nullptr;
_root = cur;
}
else
{
if (grandfather->_left == parent)
{
grandfather->_left = cur;
}
else
{
grandfather->_right = cur;
}
cur->_parent = grandfather;
}
cur->_bf = parent->_bf = 0;
}
void RotateRL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
RotateR(parent->_right);
RotateL(parent);
// 更新平衡因子
if (curleft->_bf == 0)
{
cur->_bf = 0;
parent->_bf = 0;
curleft->_bf = 0;
}
else if (curleft->_bf == 1)
{
cur->_bf = 0;
parent->_bf = -1;
curleft->_bf = 0;
}
else if (curleft->_bf == -1)
{
cur->_bf = 1;
parent->_bf = 0;
curleft->_bf = 0;
}
}
void RotateLR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
RotateL(parent->_left);
RotateR(parent);
// 更新平衡因子
if (curright->_bf == 0)
{
cur->_bf = 0;
parent->_bf = 0;
curright->_bf = 0;
}
else if (curright->_bf == 1)
{
cur->_bf = -1;
parent->_bf = 0;
curright->_bf = 0;
}
else if (curright->_bf == -1)
{
cur->_bf = 0;
parent->_bf = 1;
curright->_bf = 0;
}
}
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = _root;
Node* cur = _root;
while (cur)
{
if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
// 链接
cur = new Node(kv);
if (cur->_kv.first > parent->_kv.first)
{
parent->_right = cur;
cur->_parent = parent;
}
else
{
parent->_left = cur;
cur->_parent = parent;
}
parent = cur->_parent;
// 更新平衡因子
while (parent)
{
// 1. 先更新一下parent
// 新插在右
if (parent->_right == cur)
{
parent->_bf++;
}
else // 新插在左
{
parent->_bf--;
}
// 2. 检查更新后的parent是否影响平衡
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)
{
RotateRL(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RotateR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
else
{
assert("平衡因子更新错误!");
}
break;
}
}
return true;
}
int Height()
{
return Height(_root);
}
int Height(Node* root)
{
if (root == nullptr)
return 0;
int left = Height(root->_left);
int right = Height(root->_right);
return left > right ? left + 1 : right + 1;
}
bool Isbalance()
{
return Isbalance(_root);
}
bool Isbalance(Node* root)
{
if (root == nullptr)
return true;
int lheight = Height(root->_left);
int rheight = Height(root->_right);
if (root->_bf != rheight - lheight || abs(rheight - lheight) > 1)
{
cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
return false;
}
return Isbalance(root->_left) && Isbalance(root->_right);
}
int GetRoateCount()
{
return RotateCount;
}
private:
Node* _root = nullptr;
int RotateCount = 0;
};
}
富家不用买良田,书中自有千钟粟。
安居不用架高堂,书中自有黄金屋。
出门无车毋须恨,书中有马多如簇。
娶妻无媒毋须恨,书中有女颜如玉。
男儿欲遂平生志,勤向窗前读六经。
— — 赵恒《劝学诗》