C++ | AVLTree

🦌云深麋鹿
专栏C++ | 用C语言学数据结构 | Java

回顾:上一篇我们结束了 map&set的使用,接下来这篇文章让我们进入到 AVLTree 的学习,体会新的设计思路吧~

放个目录

一 AVL树的概念

  1. AVL树是一棵高度平衡二叉查找树,左右子树高度差的绝对值不超过1。
  2. 每个结点有一个平衡因子(balance factor),这不是必须的。
  3. 有些情况下,高度差不为0。

二 实现

2.1 AVLTreeNode
cpp 复制代码
template<class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
    int _bf; // balance factor

    AVLTreeNode(const pair<K, V>& kv)
        :_kv(kv)
        , _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _bf(0)
    {}
};
  1. 这里的Node存储的是一个pair(键值对),应用场景更广一些。
  2. 需要额外记录每个Node的_parent,后面要用到。
  3. 实现的是带平衡因子(后面都简称bf)的AVL树。
2.2 AVLTree
cpp 复制代码
template<class K, class V>
class AVLTree
{
    typedef AVLTreeNode<K, V> Node;
public:

private:
	Node* _root = nullptr;
};

搭个框架。

2.3 Insert
2.3.1 插入流程
(1)新增结点
cpp 复制代码
bool Insert(const pair<K, V>& kv)
{ 
    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->_right;
		}
		else if (cur->_kv.first > kv.first) {
			parent = cur;
			cur = cur->_left;
		}
		else {
			return false;
		}
    }
	cur = new Node(kv);
    if (parent->_kv.first > kv.first) {
        parent->_left = cur;
    }
    else {
        parent->_right = cur;
    }
    cur->_parent = parent;
    // ...
}
(2)更新平衡因子
  • 新增结点,更新 新增节点 - 根节点 路径上的bf。
  • 具体后面展开讲。
(3)旋转
  • 更新bf的过程中出现不平衡(左右子树高度差的绝对值超过1),需要旋转不平衡子树。
  • 平衡后就不用继续往上更新bf了,完毕。
2.3.2 平衡因子更新
(1)更新原则
  1. 平衡因子 = 右子树高度 - 左子树高度
  2. 子树高度变化影响当前结点平衡因子。
  3. 插入结点parent更新(新结点在左--,在右++)
  4. 若parent的bf不为零则往上更新。
(2)停止更新条件
  1. 更新后parent的bf为 零,表示parent所在子树高度不变。则parent的parent结点的bf不会受到影响,更新结束。
  2. 更新后parent的bf为 1 或 -1,表示parent所在子树原先平衡,更新后高度改变。则parent的parent结点的bf也会受到影响,往上继续更新。
  3. 更新后parent的bf为 2 或 -2,表示parent所在子树已经不平衡了,需要旋转处理。
  4. 不断更新到root,bf为 1 或 -1 停止。
(3)代码实现
  1. 先套一个框架:新增节点 的 parent 存在时,进入循环体。
cpp 复制代码
 while (parent) {
     //... 
 }
  1. 更新 parent 的 bf:
cpp 复制代码
if (parent->_kv.first > kv.first) {
    parent->_bf--;
}
else {
    parent->_bf++;
}
  1. 判断 parent 的 bf 是否为零:
cpp 复制代码
if (parent->_bf == 0) {
    break;
}else if (abs(parent->_bf) == 1) { 
    // continue
    cur = parent;
    parent = parent->_parent;
}else if (abs(parent->_bf) == 2) { 
    // rotate
    break;
}else {
    // error
    exit(-1);
}

接下来进入rotate(上面第二个else if)。

2.3.3 旋转

旋转共有四种:左旋转、右旋转、左右双旋 和 右左双旋。

(1)旋转原则
  1. 旋转后保持搜索树的特性。
  2. 让旋转的树从不平衡变平衡,其次降低旋转树的高度。
(2)右单旋

左子树太高了。

即如图中a子树新增一个结点 高h+1,太高了。

①过程

subL的右子树(subLR)变为parent结点的左子树,subL结点到parent结点的位置上。

  • parent上面还有parentParent,后面会用到。

最终实现结果:

②代码实现

框架:

cpp 复制代码
void RotateR(Node* parent){
    //...
}
  • 注意修改结点位置也要修改它的_parent。
  1. 记录各个结点(根据传参parent)
cpp 复制代码
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* parentParent = parent->_parent;
  1. 处理parent和subLR
cpp 复制代码
parent->_left = subLR;
if (subLR) {
    subLR->_parent = parent;
}
  • 存在subLR为nullptr的情况。
  1. 处理subL和parent
cpp 复制代码
subL->_right = parent;
parent->_parent = subL;
  1. 处理parentParent和subL
cpp 复制代码
if (parentParent) {
    if (parentParent->_left == parent) {
        parentParent->_left = subL;
    }
    else {
        parentParent->_right = subL;
    }
	subL->_parent = parentParent;
}else {
	_root = subL;
    _root->_parent = nullptr;
}
  • 涉及parentParent存在不存在的问题。
  • 若存在,涉及parentParent存在于哪个分支的问题。
  1. 记得更新bf
cpp 复制代码
parent->_bf = subL->_bf = 0;
(3) 左单旋

右子树太高了。

即如图中a子树新增一个结点后 高h+1:

①过程

subR的左子树(subRL)变为parent结点的右子树,subR结点到parent结点的位置上。

最终实现结果:

②代码实现

跟右单旋代码类似:

cpp 复制代码
void RotateL(Node* parent) {
	Node* subR = parent->_right;
	Node* subRL= subR->_left;
	Node* parentParent = parent->_parent;

	parent->_right = subRL;
    if (subRL) {
        subRL->_parent = parent;
    }

	subR->_left = parent;
    parent->_parent = subR;

    if (parentParent) {
		if (parentParent->_left == parent) {
			parentParent->_left = subR;
		}
		else {
			parentParent->_right = subR;
		}
		subR->_parent = parentParent;
    }else {
		_root = subR;
		_root->_parent = nullptr;
    }
	parent->_bf = subR->_bf = 0;
}
(4)左右双旋

左子树高了,左子树的右子树高了。

  • 即图中 e子树 新增结点后 高h。
  • subL 总高h+2,跟c子树高度差超过1了。

    ①过程
    subL左单旋 >> parent右单旋。
    实现结果:

    ②代码实现
    框架:
cpp 复制代码
void RotateRL(Node* parent) {
    //...
}
  1. 记录各个结点
cpp 复制代码
Node* subL = parent->_left;
Node* subLR = subL->_right;
Node* parentParent = parent->_parent;

额外记录一下bf:

cpp 复制代码
int bf = subLR->_bf;
  1. 旋转
cpp 复制代码
RotateL(parent->_left);
RotateR(parent);

这个直接复用写过的代码。

  1. 重点: 更新bf
cpp 复制代码
if (bf == 0){
	subL->_bf = 0;
	subLR->_bf = 0;
	parent->_bf = 0;
}else if (bf == -1){
	subL->_bf = 0;
	subLR->_bf = 0;
	parent->_bf = 1;
} else if (bf == 1){
	subL->_bf = -1;
	subLR->_bf = 0;
	parent->_bf = 0;
} else{
    exit(-1);
}

演示一下这几种情况:

(5)右左双旋

右子树高了,右子树的左子树高了。

  • 即图中 e子树 新增结点后 高h。

    ①过程
    subR右单旋 >> parent左单旋。
    结果:

    ②代码实现
    跟左右双旋类似,直接上代码:
cpp 复制代码
void RotateRL(Node* parent) {
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* parentParent = parent->_parent;
    int bf = subRL->_bf;
    
    RotateR(subR);
    RotateL(parent);

    if (bf == 0) {
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
    }else if (bf == -1) {
		subR->_bf = 1;
		subRL->_bf = 0;
		parent->_bf = 0;
    }else if (bf == 1) {
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = -1;
    }else {
		exit(-1);
    }
}
2.4 AVL树的平衡检测

框架:

cpp 复制代码
bool IsBalanceTree(Node* root) {
    // ...
}
  1. 处理root为空的情况
cpp 复制代码
if (root == nullptr) {
    return true;
}
  1. 记录左右子树高度
cpp 复制代码
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
  1. 计算高度差,即为 root 的实际bf
cpp 复制代码
int difference = rightHeight - leftHeight;
  1. 检查 difference 是否合理
cpp 复制代码
if(abs(difference) == 2){
	cout << root->_kv.first << "abnormal height difference" << endl;
    return false;
}
  1. 检查已存储bf是否正确
cpp 复制代码
if(difference != root->_bf){
    cout << root->_kv.first << "abnormal balance factor" << endl;
    return false;
}
  • 不能直接检查bf,因为bf可能更新错。
  1. 递归往root左右判断
cpp 复制代码
return IsBalanceTree(root->_left) && IsBalanceTree(root->_right);
2.5 AVL树的查找

没啥好说的,跟二叉搜索树一样:

cpp 复制代码
Node* Find(const K& key)
{
	Node* cur = _root;
    while (cur) {
		if (cur->_kv.first < key){
			cur = cur->_right;
		}else if (cur->_kv.first > key){
			cur = cur->_left;
		}else{
			return cur;
		}
    } 
    return nullptr;
}

AVLTree 的学习就到这里,下一篇我们再上一个新的树 红黑树 ,很快会更出来~


相关推荐
爱代码的小黄人1 小时前
总谐波畸变率 THD 的计算公式整理:MATLAB 官方公式与论文常用公式对比
开发语言·matlab
2zcode1 小时前
基于Matlab不规则颗粒粒径周长面积测量及计数系统
开发语言·算法·matlab
XerCis1 小时前
ngrok实现内网穿透(以Python FastAPI为例)
开发语言·python·fastapi·ngrok
Qt程序员1 小时前
从协议到实战:HTTP 反向代理
linux·c++·websocket·nginx·http·反向代理·正向代理
xiaoshuaishuai81 小时前
C# 继承与虚方法
开发语言·windows·c#
Hua-Jay1 小时前
OpenCV联合C++/Qt 学习笔记(十六)----图像细化、轮廓检测、轮廓信息统计及轮廓外接多边形
c++·笔记·qt·opencv·学习·计算机视觉
我滴老baby1 小时前
企业级工具链设计从单一工具到分层工具体系的架构实践
java·开发语言·架构
AI玫瑰助手1 小时前
Python流程控制:if-else与if-elif-else嵌套使用
开发语言·python·信息可视化
无限进步_1 小时前
【C++】深入底层:自己动手实现一个哈希表
开发语言·数据结构·c++·算法·链表·散列表·visual studio