AVL树简介
AVL树是 “ 搜索 平衡 二叉 树” ,我们知道 AVL树 来自 搜索 树, 我们知道 搜素树可以有效的提高数据的查找效率, 但是如果我们创建搜索树时,数据接近有序,搜索树就会退化成单支,此时我们查找的时间复杂度依然会是O(n)。
因此, 俄罗斯的两位数学家发明了AVL树解决了这个问题,AVL树的命名也是由两位数学家的名字缩写得来。
AVL树的特点是, 树中,任意一个节点的左右子树的高度之差不大于1。 这样就避免了搜索树退化成单支的问题。当AVL数有n个节点时, 搜索的复杂度可以始终保持在O(以2为底n的对数)。
AVL树的基本操作
AVL树的节点
AVL树本质上还是一颗搜索二叉树。 我们除了本来的左子树指针,右子树指针,节点值以外,我们还需要保存一个平衡因子,平衡因子用来记录该节点的左右子树高度差,同时为了我们方便操作,额外保存一个指向父节点的节点指针。
template<class K,class V>
class AVLTreeNode {
public:
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent; // 指向父亲
std::pair<K, V> _kv;
int _bf; // 平衡因子 右边高度 减去 左边高度
AVLTreeNode(const pair<K,V>& kv)
:_left(nullptr)
,_right(nullptr)
,_parent(nullptr)
,_kv(kv)
,_bf(0)
{
}
};
值得注意的是, 我们不一定需要额外保存平衡因子和父节点指针。如果不记录平衡因子,我们则可以利用栈来保存访问路径,和计算高度。相比之下,额外保存平衡因子和父节点更加方便处理。
搜索
AVL树的搜索和普通的搜索树操作并无区别,通过中序遍历 ,即可得到有序序列。
插入
平衡因子
AVL的基础依旧还是搜索树,所以插入AVL树同样是通过查找到特定位置然后插入。但是在插入后,我们需要更新各个节点的平衡因子。
例如原本蓝色部分是一颗符合规则的AVL树,当我们新插入一个红色节点时,我们自下往上更新平衡因子。如果一个节点的平衡因子达到-2或者2,此时就说明该节点的左右子树高度已经出现问题需要调整,此时我们就不需要继续向上更新节点的高度了。
那么如果我们新增的节点在父节点的右边, 则父节点的平衡因子++;
如果我们新增的节点在父节点的左边,则父节点的平衡因子–;
跟新玩父节点的平衡因子以后,如果父节点的平衡因子为0,则说明父节点的所在的子树高度不变。(因为能变成0 则说明父节点原来的平衡因子为1或者-1 ,只有将高度较低的一段新增节点后才能变成0),此时我们就可以停止向上对平衡因子的更新。 但如果父节点的更新后为1或者-1, 则说明父节点所在子树的高度变化了(因为父节点的平衡因子变成1或者-1,则说明父节点原来的平衡因子是0,此时我们新增了一个节点让一边的高度增加了。这时我们就需要继续向上更新平衡因子。
右旋
当调整完平衡因子时,有可能会出现不平衡的情况,当新节点插入较高左子树的左侧时。
例如图中这样的情况,我们就需要进行“右旋”。
我们将 cur的右子树,移给parent的左子树,然后再将parent 宜给cur的右子树,这样整棵树就会完成调整,然后cur 和parent 的平衡因子都会变为零。
//右旋
void RotateR(Node* parent) {
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR != nullptr) {
//注意调整节点 指向父节点 的指针 指向。
subLR->_parent = parent;
}
Node* parentparent =