AVL树(Adelson-Velsky and Landis Tree)是计算机科学中最早被发明的自平衡二叉查找树。在AVL树中,任一节点对应的两棵子树的最大高度差为1,因此它也被称为高度平衡树。查找、插入和删除在平均和最坏情况下的时间复杂度都是O(logN)
。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。AVL树得名于它的发明者G. M. Adelson-Velsky和Evgenii Landis,他们在1962年的论文《An algorithm for the organization of information》中公开了这一数据结构。
节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子1、0或 -1的节点被认为是平衡的。带有平衡因子 -2或2的节点被认为是不平衡的,并需要重新平衡这个树。平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。
算法 | 平均 | 最差 |
---|---|---|
空间 | O(n) | O(n) |
搜索 | O(log n) | O(log n) |
插入 | O(log n) | O(log n) |
删除 | O(log n) | O(log n) |
AVL树的基本操作一般涉及运作同在不平衡的二叉查找树所运作的同样的算法。但是要进行预先或随后做一次或多次所谓的"AVL旋转"。
以下图表以四列表示四种情况,每行表示在该种情况下要进行的操作。在左左和右右的情况下,只需要进行一次旋转操作;在左右和右左的情况下,需要进行两次旋转操作。
下面动画演示了不断将节点插入AVL树时的情况,并且演示了左旋(Left Rotation)、右旋(Right Rotation)、右左旋转(Right-Left Rotation)、左右旋转(Left-Right Rotation)以及带子树的右旋(Right Rotation with children)
从AVL树中删除,可以通过把要删除的节点向下旋转成一个叶子节点,接着直接移除这个叶子节点来完成。因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费固定的时间,所以删除处理在整体上耗费O(log n) 时间。
可以像普通二叉查找树一样的进行,所以耗费O(log n)时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查找而改变。(这是与伸展树搜索相对立的,它会因为搜索而变更树结构。)
假设平衡因子是左子树的高度减去右子树的高度所得到的值,又假设由于在二叉排序树上插入节点而失去平衡的最小子树根节点的指针为a(即a是离插入点最近,且平衡因子绝对值超过1的祖先节点),则失去平衡后进行的规律可归纳为下列四种情况:
在平衡二叉排序树AVL树(Adelson-Velsky and Landis Tree)上插入一个新的数据元素e的递归算法可描述如下:
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
pair<K, V> _kv;
int _bf;
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _bf(0)
{}
};
这个C++模板结构体,表示一个AVL树的节点(AVLTreeNode),AVL树是一种自平衡二叉搜索树。这个结构体包含了以下成员:
_left
:指向左子节点的指针。_right
:指向右子节点的指针。_parent
:指向父节点的指针。_kv
:一个键值对,用来存储节点的关键字和关联的值。_bf
:平衡因子(Balance Factor),用来表示节点的平衡状态。通常,平衡因子是左子树的高度减去右子树的高度。AVL树要求每个节点的平衡因子在[-1, 1]范围内,以保持树的平衡。这个结构体表示了AVL树中的一个节点,通常在AVL树的实现中,你会有一个指向根节点的指针来访问整个树。AVL树的节点结构包括平衡因子 _bf
是为了帮助维持树的平衡,当插入或删除节点时,需要根据平衡因子来进行相应的旋转操作,以确保树的平衡性。
这里我们首先定义结构体struct AVLTree
包含下面的成员:
typedef AVLTreeNode<K, V> Node;
private:
Node* _root = nullptr;
定义插入成员函数:
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->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
// 控制平衡
while (parent)
{
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 0)
{
break;
}
else if (abs(parent->_bf) == 1)
{
parent = parent->_parent;
cur = cur->_parent;
}
else if (abs(parent->_bf) == 2)
{
// 说明parent所在子树已经不平衡了,需要旋转处理
if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
else if ((parent->_bf == -2 && cur->_bf == -1))
{
RotateR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
return true;
}
这段代码的主要功能是往AVL树中插入一个新的键值对 kv
,并在插入后维护树的平衡性。下面是代码的主要步骤:
_root == nullptr
),则直接创建一个新的根节点 _root
并插入 kv
,然后返回。parent
和 cur
指针来遍历树,找到应该插入的位置。cur
的关键字小于 kv
的关键字,则向右子树移动,否则向左子树移动,直到找到一个空位置插入新节点。_parent
。_bf
。平衡因子的更新规则
- 新增在左,
parent->bf--
;新增在右,parent->bf++
- 更新后,
parent->bf==1 or -1
,说明parent插入前的平衡因子是0,说明左右子树高度相等,插入后有一边高,需要继续往上更新- 更新后,
parent->bf==0
,说明parent插入前的平衡因子是1 or -1
,说明左右子树一边高一边低,插入后两边一样高,插入填上了矮的那边,parent所在子树高度不变,不需要继续往上更新- 更新后,
parent->bf==2 or -2
,说明parent插入前的平衡因子是1 or -1
,已经平衡临界值,插入变成2 or -2
,parent所在子树需要旋转处理- 更新后,
parent->bf>2 or <-2
,这个条件是不成立的,如果存在,则说明插入前就存在问题,需向前检查
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种 :
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (_root == parent)
{
_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;
}
parent
的右子树 subR
和 subR
的左子树 subRL
的指针。parent
的右子树指针 _right
指向 subRL
,即将 subRL
作为新的 parent
的右子树。subRL
存在(不为 nullptr),则将 subRL
的父节点指针 _parent
指向 parent
,以确保树的连接正确。parent
的父节点指针 ppNode
,以确定如何连接 subR
。subR
的左子树指针 _left
指向 parent
,同时将 parent
的父节点指针 _parent
指向 subR
,完成左旋转。parent
是根节点(_root == parent
),则需要更新根节点 _root
为 subR
,并将 subR
的父节点指针 _parent
设置为 nullptr,以确保树的根正确连接。parent
不是根节点,根据 parent
在其父节点 ppNode
中的位置,将 subR
连接到正确的位置,更新 subR
的父节点指针 _parent
为 ppNode
。parent
和 subR
的平衡因子 _bf
设置为0,因为在左旋转后,它们的高度没有变化。void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
subL->_bf = parent->_bf = 0;
}
parent
的左子树 subL
和 subL
的右子树 subLR
的指针。parent
的左子树指针 _left
指向 subLR
,即将 subLR
作为新的 parent
的左子树。subLR
存在(不为 nullptr),则将 subLR
的父节点指针 _parent
指向 parent
,以确保树的连接正确。parent
的父节点指针 ppNode
,以确定如何连接 subL
。subL
的右子树指针 _right
指向 parent
,同时将 parent
的父节点指针 _parent
指向 subL
,完成右旋转。parent
是根节点(_root == parent
),则需要更新根节点 _root
为 subL
,并将 subL
的父节点指针 _parent
设置为 nullptr,以确保树的根正确连接。parent
不是根节点,根据 parent
在其父节点 ppNode
中的位置,将 subL
连接到正确的位置,更新 subL
的父节点指针 _parent
为 ppNode
。parent
和 subL
的平衡因子 _bf
设置为0,因为在右旋转后,它们的高度没有变化。原理同左单旋
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
subLR->_bf = 0;
if (bf == 1)//上图为例,新增节点在c下方
{
parent->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1)//上图为例,新增节点在b下方
{
parent->_bf = 1;
subL->_bf = 0;
}
else if (bf == 0)//上图为例,无其他子树,60为新增节点的情况
{
parent->_bf = 0;
subL->_bf = 0;
}
else
{
assert(false);
}
}
parent
的左子树 subL
和 subL
的右子树 subLR
的指针,以及 subLR
的平衡因子 bf
。parent
的左子树 subL
进行左旋转操作,以调整子树的结构。parent
进行右旋转操作,以将 subL
成为 parent
的右子树。subLR
的平衡因子 _bf
设置为0,因为它在旋转后的位置高度没有变化。subLR
的平衡因子 bf
的不同值来更新节点的平衡因子:
bf
为1,表示 subL
的左子树高度大于右子树,将 parent
的平衡因子设置为0,subL
的平衡因子设置为-1。bf
为-1,表示 subL
的右子树高度大于左子树,将 parent
的平衡因子设置为1,subL
的平衡因子设置为0。bf
为0,表示 subL
的左右子树高度相等,将 parent
和 subL
的平衡因子都设置为0。bf
不是1、-1或0,那么会触发 assert(false)
,表示出现了异常情况。void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
subRL->_bf = 0;
if (bf == 1)
{
subR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subR->_bf = 1;
parent->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
}
else
{
assert(false);
}
}
parent
的右子树 subR
和 subR
的左子树 subRL
的指针,以及 subRL
的平衡因子 bf
。parent
的右子树 subR
进行右旋转操作,以调整子树的结构。parent
进行左旋转操作,以将 subR
成为 parent
的左子树。subRL
的平衡因子 _bf
设置为0,因为它在旋转后的位置高度没有变化。subRL
的平衡因子 bf
的不同值来更新节点的平衡因子:
bf
为1,表示 subRL
的左子树高度大于右子树,将 subR
的平衡因子设置为0,parent
的平衡因子设置为-1。bf
为-1,表示 subRL
的右子树高度大于左子树,将 subR
的平衡因子设置为1,parent
的平衡因子设置为0。bf
为0,表示 subRL
的左右子树高度相等,将 parent
和 subR
的平衡因子都设置为0。bf
不是1、-1或0,那么会触发 assert(false)
,表示出现了异常情况。原理同先左单旋再右单旋
总结:
假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑
当pSubR的平衡因子为1时,执行左单旋
当pSubR的平衡因子为-1时,执行右左双旋
当pSubL的平衡因子为-1是,执行右单旋
当pSubL的平衡因子为1时,执行左右双旋
旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。
中序遍历可得到一个有序的序列,就说明为二叉搜索树
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
root
是否为空(即树是否为空)。如果为空,则返回,结束递归。_InOrder
函数,遍历左子树 root->_left
。这将按照升序访问左子树中的节点。root
的关键字和关联的值,通常使用 cout
输出到控制台。_InOrder
函数,遍历右子树 root->_right
。这将按照升序访问右子树中的节点。每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
节点的平衡因子是否计算正确
高度成员函数
int Height(Node* root)
{
if (root == nullptr)
return 0;
return max(Height(root->_left), Height(root->_right)) + 1;
}
root
是否为空(即树是否为空)。如果为空,则返回高度0,表示空树的高度为0。root
不为空,那么递归地调用 Height
函数来计算左子树的高度和右子树的高度。max
函数比较左子树和右子树的高度,然后加上1(当前节点的高度),得到整棵树的高度。平衡树检测函数
bool IsBalance()
{
return _IsBalance(_root);
}
private:
bool _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHT = Height(root->_left);
int rightHT = Height(root->_right);
int diff = rightHT - leftHT;
if (diff != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return abs(diff) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
root
是否为空(即树是否为空)。如果为空,则返回 true
,因为空树是平衡的。root
不为空,那么首先计算左子树和右子树的高度,分别存储在 leftHT
和 rightHT
中。diff
变量中。_bf
是否等于 diff
,如果不相等,表示平衡因子异常,输出错误信息并返回 false
。_IsBalance
函数)。true
表示当前子树是平衡的。常规场景1
{16, 3, 7, 11, 9, 26, 18, 14, 15}
特殊场景2
{4, 2, 6, 1, 3, 5, 15, 7, 16, 14}
AVL.hpp
#pragma once
#include
#include
#include
using namespace std;
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
pair<K, V> _kv;
int _bf;
AVLTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _bf(0)
{}
};
template<class K, class V>
struct AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
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->_right = cur;
}
else
{
parent->_left = cur;
}
cur->_parent = parent;
while (parent)
{
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
if (parent->_bf == 0)
{
break;
}
else if (abs(parent->_bf) == 1)
{
parent = parent->_parent;
cur = cur->_parent;
}
else if (abs(parent->_bf) == 2)
{
if (parent->_bf == 2 && cur->_bf == 1)
{
RotateL(parent);
}
else if ((parent->_bf == -2 && cur->_bf == -1))
{
RotateR(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RotateRL(parent);
}
else
{
assert(false);
}
break;
}
else
{
assert(false);
}
}
return true;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
bool IsBalance()
{
return _IsBalance(_root);
}
private:
int BalanceFactor(Node* node)
{
if (node == nullptr)
{
return 0;
}
return Height(node->_left) - Height(node->_right);
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
int leftHT = Height(root->_left);
int rightHT = Height(root->_right);
int diff = rightHT - leftHT;
if (diff != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return abs(diff) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
int Height(Node* root)
{
if (root == nullptr)
return 0;
return max(Height(root->_left), Height(root->_right)) + 1;
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
Node* ppNode = parent->_parent;
subR->_left = parent;
parent->_parent = subR;
if (_root == parent)
{
_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 RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
Node* ppNode = parent->_parent;
subL->_right = parent;
parent->_parent = subL;
if (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (ppNode->_left == parent)
{
ppNode->_left = subL;
}
else
{
ppNode->_right = subL;
}
subL->_parent = ppNode;
}
subL->_bf = parent->_bf = 0;
}
void RotateLR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
int bf = subLR->_bf;
RotateL(parent->_left);
RotateR(parent);
subLR->_bf = 0;
if (bf == 1)
{
parent->_bf = 0;
subL->_bf = -1;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subL->_bf = 0;
}
else
{
assert(false);
}
}
void RotateRL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
RotateR(parent->_right);
RotateL(parent);
subRL->_bf = 0;
if (bf == 1)
{
subR->_bf = 0;
parent->_bf = -1;
}
else if (bf == -1)
{
subR->_bf = 1;
parent->_bf = 0;
}
else if (bf == 0)
{
parent->_bf = 0;
subR->_bf = 0;
}
else
{
assert(false);
}
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
private:
Node* _root = nullptr;
};
AVLTEST.cpp
#include "AVL.hpp"
int main()
{
//int a[]={16, 3, 7, 11, 9, 26, 18, 14, 15};
int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
AVLTree<int, int> avl1;
for (auto e : a)
avl1.Insert(make_pair(e, e));
avl1.InOrder();
cout << "IsBlance:" << avl1.IsBalance() << endl;
return 0;
}