一、前言
本文将会构建一个更通俗易懂的AVL树,保证小白也能看懂,通过画图厘清AVL树的插入实现过程,代码环境为VS2019。
二、AVL树的概念
对于普通的二叉搜索树而言,虽然可以缩短查找的效率,但如果很多甚至全部的数据有序,那么这个二叉搜索树将会退化为单支树,查找效率大大降低(变成O(n))。在此基础上,有两位俄罗斯的数学家G.M.Adelson-Velskii 和 E.M.Landis 设计了这样一种算法:当在二叉搜索树中插入新节点后,如果能保证每个节点的左右子树高度差绝对值不超过1,那么就可以降低树的高度,降低树的搜索次数,这就是AVL树。
三、AVL树节点的定义
本文中模拟的AVL树中存储的数据固定为pair类型。和普通二叉搜索树不一样,AVL树中多定义了一个int类型的变量_ef,这个变量是AVL树每个节点的平衡因子,当这个节点的左树高度高于右树高度n个节点时,平衡因子大小为-n,平衡因子和保存的父节点一样,都是为后续对树的调整做准备。
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode(const pair<K, V>& data)
:_ef(0)
,_parent(nullptr)
,_left(nullptr)
,_right(nullptr)
,_data(data)
{}
int _ef;
pair<K, V> _data;
AVLTreeNode<K, V>* _parent;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
};
四、AVL树的插入
1.找到并插入节点
第一步的插入和普通的二叉搜索树一样,本文在此不过多赘述,代码如下:
// 插入
bool Insert(const pair<K, V>& data)
{
// 排除极端情况
if (_root == nullptr)
{
_root = new Node(data);
return true;
}
// 找到最终插入节点
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_data.first == data.first)
break;
else if (cur->_data.first > data.first)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_data.first < data.first)
{
parent = cur;
cur = cur->_right;
}
}
// 插入
cur = new Node(data);
if (parent->_data.first > data.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
// 重新确认平衡因子
return true;
}
2.平衡AVL树
判断是否需要执行平衡操作
首先明白一个原理:每个节点都有平衡因子,初始为0,当插入的节点为左节点时,父节点平衡因子-1;插入为右节点时,父节点平衡因子+1。
执行了插入操作之后,父节点此时存在三种情况,平衡因子分为是:①为0、②正负1、③正负2
①这说明在插入新节点之前,父节点的平衡因子为正负1的一种,插入之后为0,爷爷节点及以上节点都不会受到影响。这时是一个完整的AVL树,直接结束判断即可;
②说明插入之前父节点平衡因子为0,及没有左右子树,在父节点更新平衡因子后一定会影响到父节点,此时需要迭代当前节点和父节点,向上继续调整祖宗节点平衡因子,直到情况③或情况①为止;
③这种情况说明插入新节点后父节点平衡因子已经超出,需要进行重新调整操作(注:此处指的父节点也可能是迭代后的父节点)。
// 重新确认平衡因子
while (parent)
{
if (parent->_left == cur)
parent->_ef--;
else if (parent->_right == cur)
parent->_ef++;
// 更新完成
if (parent->_ef == 0)
break;
// 并没有更新完
else if (abs(parent->_ef) == 1)
{
cur = parent;
parent = cur->_parent;
}
// 失衡,进行调整
else if (abs(parent->_ef) == 2)
{
}
else
{
assert(false);
}
}
平衡平衡因子
此时的树一定是不平衡的,如果要树保持平衡的话,可以对树进行旋转操作,旋转分为左单转,右单旋,左双旋,右双旋。
①新节点插入在较高左子树的左侧 —— 右单旋
此处使用了最为简单的例子,实际情况中插入新节点后需要进行旋转的很可能不是新插入节点,有可能是迭代后的子树。但无论是什么,都适用于以上旋转,只要保持平衡因子绝对值不过2就能实现。 具体代码段如下:
// 右单旋
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
parent->_left = cur->_right;
// 细节处理:若存在cur的右子树,将cur右子树的父亲指向parent
if (curright)
curright->_parent = parent;
// 细节处理:祖父节点子树指向和被指向的更新
Node* pparent = parent->_parent;
cur->_right = parent;
parent->_parent = cur;
if (pparent == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = cur;
}
else
{
pparent->_right = cur;
}
cur->_parent = pparent;
}
// 细节处理:更新平衡因子
cur->_ef = parent->_ef = 0;
}
②新节点插入在较高右子树的右侧 —— 左单旋
这种情况和右单旋基本没有区别,照葫芦画瓢,代码段如下:
// 左单旋
void RotateL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
parent->_right = cur->_left;
if (curleft)
curleft->_parent = parent;
Node* pparent = parent->_parent;
cur->_left = parent;
parent->_parent = cur;
if (pparent == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = cur;
}
else
{
pparent->_right = cur;
}
cur->_parent = pparent;
}
cur->_ef = parent->_ef = 0;
}
③新节点插入在较高左子树的右侧 —— 先左单旋,再右单旋(右双旋)
这种情况,需要先对cur节点进行左单旋,再对parent节点右单旋——这很简单,直接复用已经写过的左右单旋函数即可。但实际上并没有结束,重要的是对更新后节点平衡因子的更新,此处只列举出了其中一种情况,其他两种情况将在代码中实现,感兴趣的各位可以自行去画图验证~
// 右双旋
void RotateLR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
// 左旋cur,再右旋parent
RotateL(cur);
RotateR(parent);
// 确定平衡因子
if (curright->_ef == 0)
{
parent->_ef = cur->_ef = curright->_ef = 0;
}
else if (curright->_ef == -1)
{
curright->_ef = cur->_ef = 0;
parent->_ef = 1;
}
else if (curright->_ef == 1)
{
curright->_ef = parent->_ef = 0;
cur->_ef = 1;
}
else
{
assert(false);
}
}
④新节点插入在较高右子树的左侧 —— 先右单旋,再左单旋(左双旋)
这一段和右双旋原理相同,关键在于旋转后平衡因子的重置,这点需要自己画图验证,代码实现如下:
// 左双旋
void RotateRL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
// 右旋cur,再左旋parent
RotateR(cur);
RotateL(parent);
// 确定平衡因子
if (curleft->_ef == 0)
{
parent->_ef = cur->_ef = curleft->_ef = 0;
}
else if (curleft->_ef == -1)
{
parent->_ef = curleft->_ef = 0;
cur->_ef = 1;
}
else if (curleft->_ef == 1)
{
curleft->_ef = cur->_ef = 0;
parent->_ef = -1;
}
else
{
assert(false);
}
}
解决掉所有插入后需要平衡的情况后,一颗能保证绝对平衡的二叉搜索树就此诞生啦!希望这篇文章能对您的学习有所收获。
五、源码分享
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode(const pair<K, V>& data)
:_ef(0)
,_parent(nullptr)
,_left(nullptr)
,_right(nullptr)
,_data(data)
{}
int _ef;
pair<K, V> _data;
AVLTreeNode<K, V>* _parent;
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
// 插入
bool Insert(const pair<K, V>& data)
{
// 排除极端情况
if (_root == nullptr)
{
_root = new Node(data);
return true;
}
// 找到最终插入节点
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_data.first == data.first)
break;
else if (cur->_data.first > data.first)
{
parent = cur;
cur = cur->_left;
}
else if (cur->_data.first < data.first)
{
parent = cur;
cur = cur->_right;
}
}
// 插入
cur = new Node(data);
if (parent->_data.first > data.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
// 重新确认平衡因子
while (parent)
{
if (parent->_left == cur)
parent->_ef--;
else if (parent->_right == cur)
parent->_ef++;
// 更新完成
if (parent->_ef == 0)
break;
// 并没有更新完
else if (abs(parent->_ef) == 1)
{
cur = parent;
parent = cur->_parent;
}
// 失衡,进行调整
else if (abs(parent->_ef) == 2)
{
if (parent->_ef == 2 && cur->_ef == 1)
RotateL(parent);
else if (parent->_ef == -2 && cur->_ef == -1)
RotateR(parent);
else if (parent->_ef == 2 && cur->_ef == -1)
RotateRL(parent);
else if (parent->_ef == -2 && cur->_ef == 1)
RotateLR(parent);
}
else
{
assert(false);
}
}
return true;
}
// 左单旋
void RotateL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
parent->_right = cur->_left;
if (curleft)
curleft->_parent = parent;
Node* pparent = parent->_parent;
cur->_left = parent;
parent->_parent = cur;
if (pparent == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = cur;
}
else
{
pparent->_right = cur;
}
cur->_parent = pparent;
}
cur->_ef = parent->_ef = 0;
}
// 右单旋
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
parent->_left = cur->_right;
if (curright)
curright->_parent = parent;
Node* pparent = parent->_parent;
cur->_right = parent;
parent->_parent = cur;
if (pparent == nullptr)
{
_root = cur;
cur->_parent = nullptr;
}
else
{
if (pparent->_left == parent)
{
pparent->_left = cur;
}
else
{
pparent->_right = cur;
}
cur->_parent = pparent;
}
cur->_ef = parent->_ef = 0;
}
// 左双旋
void RotateRL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
// 右旋cur,再左旋parent
RotateR(cur);
RotateL(parent);
// 确定平衡因子
if (curleft->_ef == 0)
{
parent->_ef = cur->_ef = curleft->_ef = 0;
}
else if (curleft->_ef == -1)
{
parent->_ef = curleft->_ef = 0;
cur->_ef = 1;
}
else if (curleft->_ef == 1)
{
curleft->_ef = cur->_ef = 0;
parent->_ef = -1;
}
else
{
assert(false);
}
}
// 右双旋
void RotateLR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
// 左旋cur,再右旋parent
RotateL(cur);
RotateR(parent);
// 确定平衡因子
if (curright->_ef == 0)
{
parent->_ef = cur->_ef = curright->_ef = 0;
}
else if (curright->_ef == -1)
{
curright->_ef = cur->_ef = 0;
parent->_ef = 1;
}
else if (curright->_ef == 1)
{
curright->_ef = parent->_ef = 0;
cur->_ef = 1;
}
else
{
assert(false);
}
}
private:
Node* _root = nullptr;
};