二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii
和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树是空树或者是具有以下性质的二叉搜索树:
它的左右子树都是AVL树左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)(我们用的是右子树高度减左子树高度)
像这样的一颗树,每一个节点的平衡因子的绝对值都小于2的树就称为AVL树。
如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索的时间复杂度为O( l o g 2 n log_2 n log2n)。
这里就有一个问题了,AVL树又称高度平衡二叉搜索树,那么既然是平衡搜索树,为什么不是高度相等,而是高度差不大于1呢?显然,它是做不到在任何情况下都是平衡的,为什么呢?通过以下图片你就能知道答案了。
//KV模型
template <class K, class V>
struct AVLTreeNode
{
public:
pair<K, V> _kv;
//三叉链
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
//平衡因子
int _bf;
//构造函数
AVLTreeNode(const pair<K, V>& kv)
: _kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
AVL树就是在二叉搜索树的基础上引入了平衡因子(也可以通过其它方式来控制平衡),因此AVL树也可以看成是二叉搜索树。
AVL树插入新结点分为两步:
1、按照二叉搜索树的方式插入节点。
2、检查平衡因子并调整树平衡。
假设现在插入18
调整父节点parent的平衡因子:
根据上图可以看出插入了18之后parent的平衡因子变成了1,证明插入节点后影响了以parent为根节点的这颗子树的高度,需要继续沿祖先路径检查平衡因子。
可以看到,再往上更新一次发现parent的平衡因子变成了2,所以以parent为根节点的这颗子树需要进行旋转来调整平衡,那么如何旋转呢??
现在以parent为根节点的这棵子树明显是右子树高,左子树低,所以需要进行左单旋,那么如何旋转呢??
有人说这不对啊,你这里只是随机拿了一种情况出来是这样子旋转而已,怎么能代表所有的右边高左边低的情况呢?那我们就来操作一个抽象图的左单旋。
这棵h高度的AVL树在这里举例出三种情况如下:
当h=0时:
当h=1时:
当h=2时:
当h=3,h=4……后面都是一样的,通过以上的几个例子就可以确定左单旋的方法和平衡因子以及旋转后就不用再沿祖先的路径更新的方法都是正确的。
//画图理解
void RotateL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
Node* parentParent = parent->_parent;
parent->_right = curleft;
cur->_left = parent;
if (curleft)
{
curleft->_parent = parent;
}
parent->_parent = cur;
if (parentParent == nullptr)
{
_root = cur;
cur->_parent = nullptr;//这里记得更新,曾经忘记更新这里
}
else
{
if (parent == parentParent->_left)
{
parentParent->_left = cur;
}
else
{
parentParent->_right = cur;
}
cur->_parent = parentParent;
}
cur->_bf = parent->_bf = 0;
}
对于右单旋,h=0,h=1,h=2的树的旋转和左子树的h=0,h=1以及h=2…的情况是完全类似的,参考左单旋就好了。
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
Node* parentParent = parent->_parent;
parent->_left = curright;
cur->_right = parent;
if (curright != nullptr)
{
curright->_parent = parent;
}
parent->_parent = cur;
if (parentParent == nullptr)
{
_root = cur;
cur->_parent = nullptr;//这里记得更新,曾经忘记更新这里
}
else
{
if (parent == parentParent->_left)
{
parentParent->_left = cur;
}
else if (parent == parentParent->_right)
{
parentParent->_right = cur;
}
cur->_parent = parentParent;
}
cur->_bf = parent->_bf = 0;
}
左右双旋:
情况四:
通过上面左右双旋的例子,直接观察左右双旋后的结果不难发现,双旋的本质是其实是把80的左子树给50的右指针,把80的右子树给90的左指针,最后80成为了这棵树的根,无一例外。这个已然是一个规律了。
那么这个规律又给了我们什么启示呢?
void RotateLR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
int bf = curright->_bf;
RotateL(cur);
RotateR(parent);
//画图观察,发现根据bf的值更新这三个节点的平衡因子
if (bf == 0)
{
parent->_bf = 0;
cur->_bf = 0;
curright->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
cur->_bf = 0;
curright->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
cur->_bf = -1;
curright = 0;
}
else
{
assert(false);
}
}
以上就是左右双旋需要注意的点,然后右左双旋和左右双旋的情况是完全类似的。
右左双旋:
情况一:
void RotateRL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
int bf = curleft->_bf;
RotateR(cur);
RotateL(parent);
//画图观察,发现根据bf的值更新这三个节点的平衡因子
if (bf == 0)
{
parent->_bf = 0;
cur->_bf = 0;
curleft->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
cur->_bf = 0;
curleft->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
cur->_bf = 1;
curleft = 0;
}
else
{
assert(false);
}
}
#pragma once
#include
using namespace std;
#include
#include
namespace kb
{
//KV模型
template <class K, class V>
struct AVLTreeNode
{
public:
pair<K, V> _kv;
//三叉链
AVLTreeNode<K, V>* _left;
AVLTreeNode<K, V>* _right;
AVLTreeNode<K, V>* _parent;
//平衡因子
int _bf;
//构造函数
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
template <class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
//插入
bool Insert(const pair<K, V>& kv)
{
//第一次插入时是空树,直接new一个节点即可
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
//走到这里说明这棵树不是空树,需要找到新结点插入的位置
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
//新插入的元素的key值比cur的key值小,那么应该往左边找新插入元素的插入位置
if (kv.first < cur->_kv.first)
{
parent = cur;
cur = cur->_left;
}
//新插入的元素的key值比cur的key值大,那么应该往右边找新插入元素的插入位置
else if (kv.first > cur->_kv.first)
{
parent = cur;
cur = cur->_right;
}
else
{
//如果要插入的元素已经存在,那么应该就返回false表示插入失败,因为这棵树不允许有重复元素
return false;
}
}
//走到这里证明已经找到了新插入元素的插入位置了
cur = new Node(kv);
//新结点的key比父节点的key小就插入父节点的左边,比父节点的key大就插入在父节点的右边
if (cur->_kv.first < parent->_kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//走到这里说明已经插入新结点了,这时需要更新并检查从该父节点到祖先的所有节点对应的子树是否是平衡的,
// 如果不平衡需要旋转树的节点使其平衡,如果插入后使得沿新结点的祖先的路径中的某一个节点的平衡因子
//的绝对值变成了2,则需要旋转调整树的平衡
while (parent)
{
//更新沿祖先路径的节点的平衡因子
if (cur == parent->_left)
{
parent->_bf--;
}
else if (cur == parent->_right)
{
parent->_bf++;
}
if (parent->_bf == 0)
{
//如果插入新结点后cur的父节点的平衡因子变成了0,说明新节点的插入是往父节点的
//低的那边的子树中插入了节点,平衡了以父节点为根节点的这棵子树,并且新结点的
//插入并没有影响到以父节点为根的这颗子树的高度,所以绝对不会影响到父节点往上的
//节点的平衡因子,所以往上的树不可能会出现不平衡的情况,所以无需再检查父节点到
//祖先的节点了,直接break即可
break;
}
else if (abs(parent->_bf) == 1)
{
//如果插入新结点后cur的父节点的平衡因子的绝对值变成了1,说明新结点的插入改变了以
//该父节点为根节点的这颗子树的高度,同时也会改变从该父节点往上的祖先的平衡因子,
//所以需要往上再检查祖先节点的平衡因子是否符合平衡树的规定,如果出现大于2就要旋转
cur = parent;
parent = parent->_parent;
}
else
{
//走到这里说明这棵树已经出事了,左右子树的高度差大于1了,所以需要通过旋转来调整这棵树的平衡
//如果cur的平衡因子是1并且parent的平衡因子是2,那么说明这棵树是右边高,左边低的(画图理解)
//这时需要向左旋转,这里称为左单旋
if (cur->_bf == 1 && parent->_bf == 2)
{
//左单旋
RotateL(parent);
}
else if (cur->_bf == -1 && parent->_bf == -2)
{
//右单旋
RotateR(parent);
}
else if (cur->_bf == -1 && parent->_bf == 2)
{
//右左双旋
RotateRL(parent);
}
else if (cur->_bf == 1 && parent->_bf == -2)
{
//左右双旋
RotateLR(parent);
}
else
{
assert(false);
}
//旋转后会把高的那颗子树的高度降下来,也就是说插入了一个新节点后这颗子树的高度
//增加了1,但是旋转后又把这颗子树的高度减了1,等于是插入了节点没有影响这颗旋转
//的子树的高度,也就是说也不会影响到这棵子树往祖先的路径的节点的平衡因子,所以
//往上的节点就无需再检查了,说明旋转之后就可以break了
break;
}
}
return true;
}
bool IsBalance()
{
return _IsBalance(_root);
}
void Inorder()
{
_Inorder(_root);
}
private:
size_t Height(Node* root)
{
if (root == nullptr)
{
return 0;
}
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalance(Node* root)
{
if (root == nullptr)
{
return true;
}
//计算左右子树的高度
int leftHeight = Height(root->_left);
int rightHeight = Height(root->_right);
//判断root的平衡因子是否等于右子树的高度减去左子树的高度,如果不等,证明这棵树已经出问题了
if (rightHeight - leftHeight != root->_bf)
{
cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
return false;
}
//判断当前树的右子树高度减左子树高度的绝对值是否小于2,再判断左右子树是否平衡
return abs(rightHeight - leftHeight) < 2
&& _IsBalance(root->_left)
&& _IsBalance(root->_right);
}
void _Inorder(Node* root)
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_kv.second << " ";
_Inorder(root->_right);
}
//画图理解
void RotateL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
Node* parentParent = parent->_parent;
parent->_right = curleft;
cur->_left = parent;
if (curleft)
{
curleft->_parent = parent;
}
parent->_parent = cur;
if (parentParent == nullptr)
{
_root = cur;
cur->_parent = nullptr;//这里记得更新,曾经忘记更新这里
}
else
{
if (parent == parentParent->_left)
{
parentParent->_left = cur;
}
else
{
parentParent->_right = cur;
}
cur->_parent = parentParent;
}
cur->_bf = parent->_bf = 0;
}
void RotateR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
Node* parentParent = parent->_parent;
parent->_left = curright;
cur->_right = parent;
if (curright != nullptr)
{
curright->_parent = parent;
}
parent->_parent = cur;
if (parentParent == nullptr)
{
_root = cur;
cur->_parent = nullptr;//这里记得更新,曾经忘记更新这里
}
else
{
if (parent == parentParent->_left)
{
parentParent->_left = cur;
}
else if (parent == parentParent->_right)
{
parentParent->_right = cur;
}
cur->_parent = parentParent;
}
cur->_bf = parent->_bf = 0;
}
void RotateRL(Node* parent)
{
Node* cur = parent->_right;
Node* curleft = cur->_left;
int bf = curleft->_bf;
RotateR(cur);
RotateL(parent);
//画图观察,发现根据bf的值更新这三个节点的平衡因子
if (bf == 0)
{
parent->_bf = 0;
cur->_bf = 0;
curleft->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = -1;
cur->_bf = 0;
curleft->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
cur->_bf = 1;
curleft = 0;
}
else
{
assert(false);
}
}
void RotateLR(Node* parent)
{
Node* cur = parent->_left;
Node* curright = cur->_right;
int bf = curright->_bf;
RotateL(cur);
RotateR(parent);
//画图观察,发现根据bf的值更新这三个节点的平衡因子
if (bf == 0)
{
parent->_bf = 0;
cur->_bf = 0;
curright->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
cur->_bf = 0;
curright->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
cur->_bf = -1;
curright = 0;
}
else
{
assert(false);
}
}
private:
Node* _root = nullptr;
};
void TestAVLTree1(void)
{
AVLTree<int, int> t;
t.Insert(make_pair(1, 1));
t.Insert(make_pair(-1, -1));
t.Insert(make_pair(3, 3));
t.Insert(make_pair(2, 2));
t.Insert(make_pair(5, 5));
t.Insert(make_pair(7, 7));
t.Inorder();
cout << endl;
}
void TestAVLTree2(void)
{
AVLTree<int, int> t;
t.Insert(make_pair(5, 5));
t.Insert(make_pair(2, 2));
t.Insert(make_pair(6, 6));
t.Insert(make_pair(4, 4));
t.Insert(make_pair(3, 3));
t.Inorder();
cout << endl;
}
void TestAVLTree3(void)
{
AVLTree<int, int> t;
t.Insert(make_pair(6, 6));
t.Insert(make_pair(3, 3));
t.Insert(make_pair(8, 8));
t.Insert(make_pair(1, 1));
t.Insert(make_pair(2, 2));
t.Inorder();
cout << endl;
}
void TestAVLTree4(void)
{
/*vector v = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };*/
vector<int> v = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
AVLTree<int, int> t;
for (const auto& e : v)
{
t.Insert(make_pair(e, e));
}
t.Inorder();
cout << endl;
}
void TestAVLTree5(void)
{
/*vector v = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };*/
vector<int> v;
srand((unsigned int)time(nullptr));
AVLTree<int, int> t;
int N = 1000000;
for (size_t i = 0; i < N; i++)
{
int e = rand();
t.Insert(make_pair(e, e));
}
//t.Inorder();
int ret = t.IsBalance();
cout << ret << endl;
cout << endl;
}
}
以上就是关于AVL树的重点内容啦!学习AVL树最重要的就是学习它插入元素,然后通过旋转控制平衡的过程,至于AVL树删除元素大家有兴趣的可以去学习一下,在《算法导论》这本书中有介绍,大概思路也是先删除元素,如果删除元素后某一棵子树的平衡因子不满足要求就通过旋转调整树的平衡。好了,今天就聊到这里,如果感觉到这篇文章对你有所帮助的话,点个小心心,点点关注呗,后期还会持续更新C++相关的知识哦,我们下期见啦!!!!!!!