高级数据结构与算法 | AVL树 (高度平衡树)
文章目录
- AVL树
- 实现思路
- 数据结构
- 查找
- 平衡因子
- 旋转
- 右旋
- 左旋
- 右左双旋
- 左右双旋
- 插入
- 删除
- AVL树的验证
- 中序遍历
- 平衡判断
- AVL树的性能
- 完整代码实现
AVL树
AVL树是最先发明的自平衡二叉查找树。在AVL树中任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。AVL树得名于它的发明者G.
M. Adelson-Velsky和E. M. Landis
AVL树其实就是在二叉搜索树的基础上,引入了平衡因子的概念,通过旋转来调整平衡因子,使得二叉树始终平衡,效率更高。
特点:
- 本身首先是一棵二叉搜索树。
- 带有平衡条件:每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。
二叉搜索树的实现的博客之前写过,所以这里就直接对以前的代码进行改造。
数据结构 : 二叉搜索树的原理以及实现(C++)
实现思路
数据结构
这里不仅需要左右子树,因为涉及到了大量的平衡因子调节,所以还需要保存父节点的指针,要用到三叉链的结构。
struct AVLTreeNode{AVLTreeNode(const std::pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;std::pair<K, V> _kv; //键值对int _bf; //平衡因子};
查找
AVL树本质还是二叉搜索树,所以查找部分可以直接复用,不需要修改。
直接从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树,相同则返回。如果遍历完还没找到,则说明不存在此树中,返回nullptr
Node* Find(const K& key)
{//根据二叉搜索树的性质,从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树Node* cur = _root;while (cur){//比根节点大则查找右子树if (key > cur->_kv.first){cur = cur->_right;}//比根节点小则查找左子树else if (key < cur->_kv.first){cur = cur->_left;}//相同则返回else{return cur;}}//遍历完则说明查找不到,返回falsereturn nullptr;
}
要讲插入和删除之前,就必须得讲他们两个的核心步骤,旋转和平衡因子的调节。AVL树正是通过这两个步骤来实现其高度平衡的特性。
平衡因子
平衡因子,其实就是左右子树的高度差。AVL树通过控制高度差不超过2,来实现平衡。
通常认为在右边插入节点时,平衡因子+1,左边插入时平衡因子减一。
例如:
右子树插入一个90,根节点平衡因子+1
当某节点平衡因子为0时,说明他的左右子树平衡
当平衡因子为1或者-1时,说明左右子树存在高度差,其父节点可能存在不平衡,需要向上继续判断。
当平衡因子为2或者-2时,说明此时不平衡,需要旋转处理。
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){//如果右边不平衡,并且子节点也是右边偏重,则左单旋if (cur->_bf == 1){RotateL(parent);}//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子else{RotateRL(parent);}}else{//左边不平衡,并且子节点也是左边偏重,右单旋if (cur->_bf == -1){RotateR(parent);}//同上,左右双旋else{RotateLR(parent);}}//旋转完后恢复平衡,更新结束。break;
}
旋转
旋转分为四种情景,简单点总结的话就是如果节点呈直线则单旋,折线则双旋,下面一一分析。
首先讨论直线状态的单旋。
直线状态,也就是某一边不平衡,并且其那一边的子节点也是朝那一边偏重。
例如这个图,左边不平衡,子节点30也是左边偏重,则单旋。
右旋
例如这种情况,当左边不平衡,并且节点呈直线时(左节点的左边偏重),说明需要右旋处理。
此时节点60的平衡因子为-2,说明此时60的位置不平衡,需要旋转,由于是左边偏重,则需要将60向右旋转来恢复平衡。
这就是最简单的右旋。但是通常情况下,这些节点还有各自的子树。
还是按照上面的旋转方法,但是要在保持原有结构的情况下稍微处理。
对于节点60,如果要将其右旋,就需要让他变成30的右节点,但是30的右节点本来就有数据,所以此时就需要将30的右节点40放到别的位置。那么应该放到哪个位置呢?很简单,因为我们需要将60右旋,所以原本是他左节点的30变成了他的父节点,而他左节点的位置空了出来,所以就可以将40放到60的左边。这时再将60整个变为30的右节点即可。并且因为此时原本的父节点60变为了30的子节点,所以还要处理其与祖父节点的关系。
旋转结束后,调整30和60的平衡因子为0
总结一下:
右旋主要分为3个步骤
- 让不平衡的结点parent的左子树变为其原本左子树subL的右节点subLR
- 让parent变为subL的右子树
- 调整新的父节点subL与祖父节点的关系,并调整旋转后的平衡因子
//右旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;//如果subLR存在,则让他的父节点指向parent。if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;//两种情况//如果parent为根节点,则让subL成为新的根节点if (parent == _root){_root = subL;subL->_parent = nullptr;}//如果不是根节点,则改变subL与其祖父节点的指向关系else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}//左旋完成后平衡,平衡因子归零。subL->_bf = parent->_bf = 0;
}
左旋
左旋的思路和右旋一样,只是将顺序翻了过来。
例如这种情况,当右边不平衡,并且节点呈直线时(右节点的右边偏重),说明需要左旋处理。
这就是最简单的左旋。
下面看看复杂的左旋
还是同样的思路。只是这次把左右调换
左旋的步骤:
- 让不平衡的结点parent的右子树变为其原本右子树subR的右节点subRL
- 让parent变为subL的左子树
- 调整新的父节点subL与祖父节点的关系,并调整旋转后的平衡因子
//左旋
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL){subRL->_parent = parent;}subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}subR->_bf = parent->_bf = 0;
}
下面讨论双旋的情景,双旋发生在折线状态时。
例如
当某边不平衡,并且对于方向的子树,其反方向偏重时,需要双旋。
例如上图,左边不平衡,而左子树则是右边偏重。此时呈折线状态。
右左双旋
继续刚刚那个图,可以看到,他呈折线状态,与前面单旋的状态都不同。所以此时我们可以换个思路,可以先对其进行一次单旋,将其转换成之前的直线状态,再进行一次单旋即可平衡。
因为其右子树是左边偏重,所以对其右子树先进行一次右旋。
可以看到,此时就恢复成了原本直线的状态,此时因为30节点右边不平衡,所以再进行一次左旋即可完成。
但是这里并不能直接进行一次右单旋然后左单旋,这里有两种情况,如果40插入在了60的左子树,则是上面那种情况,调整完后subR平衡因子为1,parent和subRL为0。
但是如果将70插入在了60的右子树,则又是另一种情况。
下面画图
如果40插入在了60的右子树,则调整完后parent平衡因子为-1,subR和subRL为0。
总结一下:
右左双旋的步骤如下
- 首先因为不平衡那个方向的子树的反方向偏重,呈折现状态,所以需要对其右旋转,让树恢复到直线状态
- 直线状态时就和一开始的单旋思路一样,按照单旋处理
- 调节平衡因子,根据subRL一开始的平衡因子进行调节,有两种情况,为-1时subR结束后为1,为1时parent结束后为-1。
//右左双旋
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;//这里需要保存subRL的平衡因子,来调节旋转完后的平衡因子int bf = subRL->_bf;//先右单旋将折线结构转换为直线结构,也就是前面单旋就可以解决的问题。RotateR(subR);//然后再左单旋即可RotateL(parent);//根据subRL的bf来调节旋转后的平衡因子if (bf == 1){parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else{parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}
}
同上思路,就不多说了,直接反过来方向就行。
左右双旋
//左右双旋
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}
}
插入
插入分为三个步骤
- 按照二叉搜索树的规则找到合适的位置插入
- 更新插入后的平衡因子
- 根据平衡因子来选择是否进行旋转调节。
思路前面已经讲了,这里直接复用代码就行,具体步骤也写在注释里
bool Insert(const std::pair<K, V>& kv)
{//按照二叉搜索树的规则先找到位置if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;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;}else{parent->_left = cur;}cur->_parent = parent;//更新平衡因子while (parent){//更新父节点的平衡因子if (cur == parent->_left){parent->_bf--;}else{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){//如果右边不平衡,并且子节点也是右边偏重,则左单旋if (cur->_bf == 1){RotateL(parent);}//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子else{RotateRL(parent);}}else{//左边不平衡,并且子节点也是左边偏重,右单旋if (cur->_bf == -1){RotateR(parent);}//同上,左右双旋else{RotateLR(parent);}}//旋转完后恢复平衡,更新结束。break;}}return true;
}
删除
AVL树的删除极为复杂,数据结构,算法导论这些书里仅仅只是提及了思路而没有实现。我自己实现了发现也大概要100多行,考虑的情况极为复杂。
虽然代码极为复杂,但是思路还是很简单,分为以下几步。
- 按照二叉搜索树的规则删除
- 更新平衡因子,并且进行旋转来调整(最坏情况下可能会一直调整到根节点)。
这里就直接复用上面平衡因子更新的代码以及之前博客实现的二叉搜索树的删除(博客链接在开始处),将其合并处理即可。
思路之前写过,就不再单独提出来说,直接写在注释里。
bool erase(const K& key)
{//删除直接按照二叉搜索树的规则删除,然后再进行平衡因子的更新即可Node* cur = _root;Node* parent = cur;/*删除有三种情况,一种是删除叶子节点,可以直接删除第二种情况,如果删除的节点只有一个子树,那么删除这个节点后,就让父节点指向他的这一子树前两种情况可以合并处理第三种情况则是左右子树都不为空,此时选择一个来节点来替换他后,再删除,就可以不破坏原有结构如果要保持原有结构不变化,那么选择的节点必须要和删除节点在中序遍历中是连续的,而满足的只有两个节点,一个是其左子树的最大值,一个是其右子树的最小值。*///删除部分while (cur){//找到删除的位置if (key > cur->_kv.first){parent = cur;cur = cur->_right;}else if (key < cur->_kv.first){parent = cur;cur = cur->_left;}else{//前两种情况合并处理,如果当前结点只有一个子树,则让父节点指向他的子树//处理只有右子树时 if (cur->_left == nullptr){//如果当前节点为根节点,则让右子树成为新的根节点if (cur == _root){_root = cur->_left;}else{//判断当前节点是他父节点的哪一个子树if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;}//处理只有左子树时 else if (cur->_right == nullptr){//如果当前节点为根节点,则让左子树成为新的根节点if (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;}//处理左右子树都不为空时,选取左子树的最右节点或者右子树的最左节点else{//这里我选取的是左子树的最右节点Node* LeftMax = cur->_left;Node* LeftMaxParent = cur;//找到左子树的最右节点while (LeftMax->_right){LeftMaxParent = LeftMax;LeftMax = LeftMax->_right;}//替换节点std::swap(cur->_kv, LeftMax->_kv);//判断当前节点是他父节点的哪一个子树, 因为已经是最右子树了,所以这个节点的右子树为空,但是左子树可能还有数据,所以让父节点指向他的左子树//并且删除最右节点if (LeftMax == LeftMaxParent->_left){LeftMaxParent->_left = LeftMax->_left;}else{LeftMaxParent->_right = LeftMax->_left;}delete LeftMax;}//删除成功,中断break;}}//查找不到if (cur == nullptr)return false;//更新平衡因子while (parent){//更新父节点的平衡因子,注意这里和插入是反过来的,因为是删除if (cur == parent->_left){parent->_bf++;}else{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){//如果右边不平衡,并且子节点也是右边偏重,则左单旋if (cur->_bf == 1){RotateL(parent);}//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子else{RotateRL(parent);}}else{//左边不平衡,并且子节点也是左边偏重,右单旋if (cur->_bf == -1){RotateR(parent);}//同上,左右双旋else{RotateLR(parent);}}//旋转完后恢复平衡,更新结束。break;}}return true;
}
AVL树的验证
如要验证AVL树,有两个方法。
一是判断其是否具有二叉搜索树的特性,可以通过中序遍历来看看是否有序来判断。
二是通过判断他所有的子树是否两边高度平衡,来判断其是否具有平衡的特性。
中序遍历
void _InOrderTravel(Node* root) const{if (root == nullptr)return;_InOrderTravel(root->_left);std::cout << root->_kv.first << ':' << root->_kv.second << std::endl;_InOrderTravel(root->_right);}void InOrderTravel() const{_InOrderTravel(_root);}
测试代码
int main()
{lee::AVLTree<int, string> tree;tree.Insert(make_pair(3, "php"));tree.Insert(make_pair(1, "c++"));tree.Insert(make_pair(2, "c#"));tree.Insert(make_pair(7, "go"));tree.Insert(make_pair(11, "js"));tree.Insert(make_pair(19, "lua"));tree.Insert(make_pair(5, "sql"));tree.Insert(make_pair(8, "java"));tree.Insert(make_pair(4, "python"));tree.InOrderTravel();return 0;
}
这一部分测试是没有问题的。
平衡判断
int countHeight(Node* root) const{if (root == nullptr)return 0;int leftHeight = countHeight(root->_left);int rightHeight = countHeight(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}bool _IsBalance(Node* root) const{if (root == nullptr)return true;int leftHeight = countHeight(root->_left);int rightHeight = countHeight(root->_right);return abs(leftHeight - rightHeight) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}bool IsBalance() const{return _IsBalance(_root);}
测试代码
int main()
{lee::AVLTree<int, string> tree;tree.Insert(make_pair(3, "php"));tree.Insert(make_pair(1, "c++"));tree.Insert(make_pair(2, "c#"));tree.Insert(make_pair(7, "go"));tree.Insert(make_pair(11, "js"));tree.Insert(make_pair(19, "lua"));tree.Insert(make_pair(5, "sql"));tree.Insert(make_pair(8, "java"));tree.Insert(make_pair(4, "python"));cout << tree.IsBalance();return 0;
}
可以看到,这个特性也满足。说明这棵树的实现没有问题。
AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证
查询时高效的时间复杂度,即 log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:
插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,
但一个结构经常修改,就不太适合。
完整代码实现
#pragma once
#include<iostream>namespace lee
{template<class K, class V>struct AVLTreeNode{AVLTreeNode(const std::pair<K, V>& kv): _left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;std::pair<K, V> _kv;int _bf;};template<class K, class V>class AVLTree{public:typedef AVLTreeNode<K, V> Node;AVLTree() : _root(nullptr){}~AVLTree(){destory(_root);}//右旋void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;//如果subLR存在,则让他的父节点指向parent。if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;//两种情况//如果parent为根节点,则让subL成为新的根节点if (parent == _root){_root = subL;subL->_parent = nullptr;}//如果不是根节点,则改变subL与其祖父节点的指向关系else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}//左旋完成后平衡,平衡因子归零。subL->_bf = parent->_bf = 0;}//左旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL){subRL->_parent = parent;}subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subR;}else{ppNode->_right = subR;}subR->_parent = ppNode;}subR->_bf = parent->_bf = 0;}//右左双旋void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;//这里需要保存subRL的平衡因子,来调节旋转完后的平衡因子int bf = subRL->_bf;//先右单旋将折线结构转换为直线结构,也就是前面单旋就可以解决的问题。RotateR(subR);//然后再左单旋即可RotateL(parent);//根据subRL的bf来调节旋转后的平衡因子if (bf == 1){parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else{parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}}//左右双旋void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;RotateL(subL);RotateR(parent);if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}}bool Insert(const std::pair<K, V>& kv){//按照二叉搜索树的规则先找到位置if (_root == nullptr){_root = new Node(kv);return true;}Node* parent = nullptr;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;}else{parent->_left = cur;}cur->_parent = parent;//更新平衡因子while (parent){//更新父节点的平衡因子if (cur == parent->_left){parent->_bf--;}else{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){//如果右边不平衡,并且子节点也是右边偏重,则左单旋if (cur->_bf == 1){RotateL(parent);}//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子else{RotateRL(parent);}}else{//左边不平衡,并且子节点也是左边偏重,右单旋if (cur->_bf == -1){RotateR(parent);}//同上,左右双旋else{RotateLR(parent);}}//旋转完后恢复平衡,更新结束。break;}}return true;}Node* Find(const K& key){//根据二叉搜索树的性质,从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树Node* cur = _root;while (cur){//比根节点大则查找右子树if (key > cur->_kv.first){cur = cur->_right;}//比根节点小则查找左子树else if (key < cur->_kv.first){cur = cur->_left;}//相同则返回else{return cur;}}//遍历完则说明查找不到,返回falsereturn nullptr;}bool erase(const K& key){//删除直接按照二叉搜索树的规则删除,然后再进行平衡因子的更新即可Node* cur = _root;Node* parent = cur;/*删除有三种情况,一种是删除叶子节点,可以直接删除第二种情况,如果删除的节点只有一个子树,那么删除这个节点后,就让父节点指向他的这一子树前两种情况可以合并处理第三种情况则是左右子树都不为空,此时选择一个来节点来替换他后,再删除,就可以不破坏原有结构如果要保持原有结构不变化,那么选择的节点必须要和删除节点在中序遍历中是连续的,而满足的只有两个节点,一个是其左子树的最大值,一个是其右子树的最小值。*///删除部分while (cur){//找到删除的位置if (key > cur->_kv.first){parent = cur;cur = cur->_right;}else if (key < cur->_kv.first){parent = cur;cur = cur->_left;}else{//前两种情况合并处理,如果当前结点只有一个子树,则让父节点指向他的子树//处理只有右子树时 if (cur->_left == nullptr){//如果当前节点为根节点,则让右子树成为新的根节点if (cur == _root){_root = cur->_left;}else{//判断当前节点是他父节点的哪一个子树if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;}//处理只有左子树时 else if (cur->_right == nullptr){//如果当前节点为根节点,则让左子树成为新的根节点if (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;}//处理左右子树都不为空时,选取左子树的最右节点或者右子树的最左节点else{//这里我选取的是左子树的最右节点Node* LeftMax = cur->_left;Node* LeftMaxParent = cur;//找到左子树的最右节点while (LeftMax->_right){LeftMaxParent = LeftMax;LeftMax = LeftMax->_right;}//替换节点std::swap(cur->_kv, LeftMax->_kv);//判断当前节点是他父节点的哪一个子树, 因为已经是最右子树了,所以这个节点的右子树为空,但是左子树可能还有数据,所以让父节点指向他的左子树//并且删除最右节点if (LeftMax == LeftMaxParent->_left){LeftMaxParent->_left = LeftMax->_left;}else{LeftMaxParent->_right = LeftMax->_left;}delete LeftMax;}//删除成功,中断break;}}//查找不到if (cur == nullptr)return false;//更新平衡因子while (parent){//更新父节点的平衡因子if (cur == parent->_left){parent->_bf++;}else{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){//如果右边不平衡,并且子节点也是右边偏重,则左单旋if (cur->_bf == 1){RotateL(parent);}//如果右边不平衡,而子节点是左边偏重,此时需要先转换为上面的状态,先右单旋再左单旋。但是不能直接右单旋再左单旋,还需要根据情况处理平衡因子else{RotateRL(parent);}}else{//左边不平衡,并且子节点也是左边偏重,右单旋if (cur->_bf == -1){RotateR(parent);}//同上,左右双旋else{RotateLR(parent);}}//旋转完后恢复平衡,更新结束。break;}}return true;}void _InOrderTravel(Node* root) const{if (root == nullptr)return;_InOrderTravel(root->_left);std::cout << root->_kv.first << ':' << root->_kv.second << std::endl;_InOrderTravel(root->_right);}void InOrderTravel() const{_InOrderTravel(_root);}int countHeight(Node* root) const{if (root == nullptr)return 0;int leftHeight = countHeight(root->_left);int rightHeight = countHeight(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}bool _IsBalance(Node* root) const{if (root == nullptr)return true;int leftHeight = countHeight(root->_left);int rightHeight = countHeight(root->_right);return abs(leftHeight - rightHeight) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}bool IsBalance() const{return _IsBalance(_root);}void destory(Node*& root){Node* node = root;if (!root)return;destory(node->_left);destory(node->_right);delete node;node = nullptr;}private:Node* _root;};
};
高级数据结构与算法 | AVL树 (高度平衡树)相关推荐
- 数据结构与算法——AVL树类的C++实现
关于AVL树的简单介绍能够參考: 数据结构与算法--AVL树简单介绍 关于二叉搜索树(也称为二叉查找树)能够參考:数据结构与算法--二叉查找树类的C++实现 AVL-tree是一个"加上了额 ...
- 高级数据结构与算法 | B树、B+树、B*树
文章目录 搜索结构 B树 B树的插入 B树的遍历 B树的性能 B+树 B+树的插入 B+树的遍历 B*树 B*树的插入 总结 搜索结构 如果我们有大量的数据需要永久存储,就需要存储到硬盘之中,但是硬盘 ...
- 数据结构与算法--B树原理及实现
B树 前几篇文中讨论的数据结构我们都是假设所有的数据都存储在计算机的主存中.可说总要那么海量的数据需要通过个中数据结构去存储,我们不可能有这么多内存区存放这些数据.那么意味着我们需要将他们放磁盘.所以 ...
- 高级数据结构实现——自顶向下伸展树
[0]README 1) 本文部分内容转自 数据结构与算法分析,旨在理解 高级数据结构实现--自顶向下伸展树 的基础知识: 2) 源代码部分思想借鉴了数据结构与算法分析,有一点干货原创代码,for o ...
- 高级数据结构与算法 | LFU缓存机制(Least Frequently Used)
文章目录 LFUCache 结构设计 LFUCache的实现 在之前我写过一篇LRU的博客,如果不了解的建议先看看这篇 高级数据结构与算法 | LRU缓存机制(Least Recently Used) ...
- 《数据结构与算法》——树与二叉树之遍历总结
<数据结构与算法>--树与二叉树之遍历总结 树与二叉树部分计划分为三次进行复习总结,第一次为基本概念和二叉树的遍历,第二次内容为线索二叉树以及树和森林,第三次为树与二叉树的应用. 目录 & ...
- 数据结构与算法 AVL查找树
1-1 The inorder traversal sequence of an AVL tree must be in sorted (non-decreasing) order. T AVL 树的 ...
- 3. 数据结构--二叉树 BST AVL树 Huffman
数据结构–二叉树 KEY:(不敢相信没有堆-) 二叉树的定义及其主要特征 ☑️ 二叉树的顺序存储结构和链式存储结构实现 二叉树的遍历及应用 二叉排序(查找.检索)树 (BST) 平衡的二叉检索树- A ...
- 数据结构:关于AVL树的平衡旋转详解
前言 本文是基于你已经有一定的二叉排序树知识.如果你还是小白,可以参考我之前的博客:<数据结构:二叉搜索树(BST)的基本操作>.所以,在本文中不会再出现关于BST树的基本知识. 版权说明 ...
最新文章
- Enterprise Library 2.0 Hands On Lab 翻译(1):数据访问程序块(一)
- QTableView中修改某个单元格或者行或者列内容颜色
- 精准营销的核心思维何在?
- java 正则表达式 替换字符串img标签的路径_python面试题汇总第06期-正则表达式(内附7题及答案)...
- kotlin学习笔记——内联函数
- Linux 查找指定文件并删除
- Tomcat(Windows)
- html怎么判断字段,javascript怎么判断是否为字符串?
- 基于机器学习的GitHub敏感信息泄露监控
- Android 内存泄漏分析与解决方法
- 《Android 源码设计模式解析与实战》— Android 书籍
- 《机械设计基础》题库
- PowerPC技术与市场杂谈
- 算法:计算股票的最大收益(动态规划经典案例)
- Daftart.ai:人工智能专辑封面生成器
- 【用pandas_alive几行代码绘制竞赛动图】全网首发pandas_alive数据可视化中文学习笔记合集,学不会来打我(配置好的venv虚拟环境+拿来即用测试代码+测试数据集+参数api解析)
- CSDN《原力计划—打卡挑战》为你而来,新升级, 多流量,抓住春季的小尾巴,冲冲冲!
- Oracle安装之1521端口及任何端口被占用错误
- EAUML日拱一卒-活动图::OMG UML2.5中文译文 16.12 Expansion Regisons
- 老笔记本电脑系统Linux,【转载】旧电脑新衣裳,国产linux操作系统旧平台短期体验...