AVL树的提出:
二叉搜索树虽然可以缩短查找的效率, 但如果数据有序, 或者接近有序, 二叉搜索树将退化为单支树, 查找元素相当于在顺序表中搜索元素, 效率低下.
二叉搜索树实现及其性能分析
由此,1962年G.M.Adelson-Velskii和E.M.Landis两位俄罗斯数学家提出平衡二叉搜索树的方法: 当向二叉搜索树中插入新结点后, 保证每个结点的左右子树高度之差的绝对值不超过1(超过了则对树的结点进行相应的调整), 即可降低树的高度, 从而减少平均搜索长度.
概念:
一颗AVL树或者是空树, 或者是具有以下性质的树:
- 它的左右子树都是AVL树
- 它的左右子树高度之差(简称平衡因子)的绝对值不超过1
平衡因子: 将二叉树上节点的右子树高度减去左子树高度的值称为该节点的平衡因子BF(Balance Factor)。
如下图 平衡二叉搜索树(AVL树) 所示:
节点5的左子树高度为3,右子树高度为3,BF= 3-3 = 0;
节点3的左子树高度为2,右子树高度为1,BF= 1-2 = -1;
节点7的左子树高度为1,右子树高度为2,BF= 2-1 = 1;
对于平衡二叉树,平衡因子的取值范围为[-1,1]。如果某个节点的BF值不在此范围,则需要对树进行相应的调整,使其重新保持平衡.
非平衡二叉搜索树
实现详解:
AVL树结点定义:
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _bf(0)
{}
// 结点的左右孩子结点和父结点
AVLTreeNode<T> *_left;
AVLTreeNode<T> *_right;
AVLTreeNode<T> *_parent;
T _data;
int _bf; // 结点的平衡因子
};
AVL树的旋转
插入或者删除结点都有可能导致 AVL树的失衡, 此时必须调整树的结构,使之平衡化.
具体要旋转的情况:
假设以 parent 结点 为根的树不平衡, 即 parent 的平衡因子为 2或者-2
- 假设结点 parent 的平衡因子为2, 说明 parent 结点的右子树高, 设parent结点的右子树的根为subR
- 当subR的平衡因子为1时,执行左单旋
- 当subR的平衡因子为-1时,执行右左双旋
- parent的平衡因子为-2,说明parent的左子树高,设parent的左子树的根为subL
- 当subL的平衡因子为-1是,执行右单旋
- 当subL的平衡因子为1时,执行左右双旋
旋转完成后,原parent为根的子树个高度降低,已经平衡,不需要再向上更新。
根据结点的插入位置不同, AVL树的旋转分为四种:
- 右单旋:
新插入结点位于较高左子树的左侧
上图在插入前,AVL树是平衡的,新节点插入到30结点的左子树中,30结点的左子树增加了一层,导致以60结点为根的二叉树不平衡,要让60结点平衡,只能将60结点的左子树的高度减少一层,右子树增加一层,即-------将左子树往上提,这样60结点转下来,因为60比30大,只能将其放在30结点的右子树,而如果30结点有右子树,右子树结点的值一定大于30,小于60,只能将其放在60结点的左子树,旋转完成后,更新结点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:
- 30节点的右孩子结点可能存在,也可能不存在
- 60可能是根节点,也可能是子树
如果是根节点,旋转完成后,要更新根节点
如果是子树,可能是某个结点的左子树,也可能是右子树
// 右单旋
void RRotate(Node *parent)
{
// 记录parent结点的 左孩子结点 sub 和sub的右孩子结点 subR
Node *sub = parent->_left;
Node *subR = sub->_right;
// 旋转完成后, subR 作为parent 的左孩子
parent->_left = subR;
// 如果subR存在, 更新双亲
if (subR)
subR->_parent = parent;
// parent 作为 sub的右孩子
sub->_right = parent;
// 1. parent为根---sub变为根, sub的祖先指向 nullptr
// 2. parent不为根---记录parent的根结点parentparent ,并使之与sub建立连接
if (parent == _root)
{
_root = sub;
sub->_parent = nullptr;
parent->_parent = sub;
}
else
{
Node *parentparent = parent->_parent;
sub->_parent = parentparent;
parent->_parent = sub;
if (parent == parentparent->_left)
parentparent->_left = sub;
else
parentparent->_right = sub;
}
parent->_bf = 0;
sub->_bf = 0;
}
- 左单旋
新插入结点位于较高右子树的右侧
实际处理情况与上面右单旋情况类似, 不在赘述.
// 左单旋
void LRotate(Node* parent)
{
// 记录当前结点prent 的右孩子结点sub 和 右孩子的左孩子结点subL
Node *sub = parent->_right;
Node *subL = sub->_left;
// parent 与 subL 建立联系
parent->_right = subL;
if (subL)
subL->_parent = parent;
// parent 与 sub 建立联系---完成旋转
// 两种情况:
// 1. parent为根---sub变为根, sub的祖先指向 nullptr
// 2. parent不为根---记录parent的根结点parentparent ,并使之与sub建立连接
sub->_left = parent;
if (parent == _root)
{
_root = sub;
sub->_parent = nullptr;
parent->_parent = sub;
}
else
{
Node *parentparent = parent->_parent;
sub->_parent = parentparent;
parent->_parent = sub;
if (parent == parentparent->_left)
parentparent->_left = sub;
else
parentparent->_right = sub;
}
parent->_bf = 0;
sub->_bf = 0;
}
-
双旋–先先左单旋在右单旋
新节点插入到较高左子树的右侧
如上图所示: 先对30结点进行左单旋, 然后在对90进行右单旋
此处双旋完成后必须考虑平衡因子的更新问题 -
双旋–先右单旋再左单旋
新节点插入较高右子树的左侧
AVL树的验证:
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
- 验证其为二叉搜索树
- 如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
- 验证其为平衡树
- 每个节点子树高度差的绝对值不超过1(即平衡因子绝对值不能大于1).我们的代码中 平衡因子是单独进行操作, 没有通过左右子树高度差来决定所以,我们会使用左右子树的高度差来验证节点的平衡因子是否计算正确.
// 计算以 root 为根 树的高度
int Highly(Node *root)
{
if (root == nullptr)
return 0;
int lefthigh = Highly(root->_left);
int righthigh = Highly(root->_right);
if (lefthigh > righthigh)
return lefthigh + 1;
else
return righthigh + 1;
}
bool _IsBalance(Node *root)
{
if (root == nullptr)
return true;
int lefthigh = Highly(root->_left);
int righthigh = Highly(root->_right);
int diff = righthigh - lefthigh;
if (diff != root->_bf)
{
cout << "结点" << root->_data << "左右子树高度差:" << diff << "平衡因子:" << root->_bf << endl;
return false;
}
return abs(diff < 2) && _IsBalance(root->_left) && _IsBalance(root->_right);
}
// 中序遍历 打印
void _InOrder(Node* root)
{
//Node *root = this->_root;
if (root != nullptr)
{
_InOrder(root->_left);
cout << root->_data << " ";
_InOrder(root->_right);
}
}
AVL树的查找:(非递归)
// 非递归查找
// 若树中存在要查找的元素, 返回该元素所在结点, 否则返回空
Node* Find(T data)
{
Node *cur = this->_root;
while (cur != nullptr)
{
if (cur->_data == data)
return cur;
else if (cur->_data > data)
cur = cur->_left;
else
cur = cur->_right;
}
return nullptr;
}
AVL树的销毁:
// 后序遍历销毁二叉树
// 先销毁左子树, 在销毁右子树
void Destory(Node* &root)
{
if (root != nullptr)
{
Destory(root->_left);
Destory(root->_right);
delete root;
root = nullptr;
}
}
性能分析:
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即(log N)但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此: 如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
完整代码:
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _data(data)
, _bf(0)
{}
// 结点的左右孩子结点和父结点
AVLTreeNode<T> *_left;
AVLTreeNode<T> *_right;
AVLTreeNode<T> *_parent;
T _data;
int _bf; // 结点的平衡因子
};
template<class T>
class AVLTree
{
typedef AVLTreeNode<T> Node;
public:
AVLTree()
: _root(nullptr)
{}
~AVLTree()
{
Destory(_root);
}
AVLTree(const AVLTree &t)
{
return t;
}
/*
T &operator [](const T& data)
{
Node* ret = Insert(data);
return ret->_data;
}
*/
private:
Node *_root;
// 后序遍历销毁二叉树
// 先销毁左子树, 在销毁右子树
void Destory(Node* &root)
{
if (root != nullptr)
{
Destory(root->_left);
Destory(root->_right);
delete root;
root = nullptr;
}
}
public:
// 非递归查找
// 若树中存在要查找的元素, 返回该元素所在结点, 否则返回空
Node* Find(T data)
{
Node *cur = this->_root;
while (cur != nullptr)
{
if (cur->_data == data)
return cur;
else if (cur->_data > data)
cur = cur->_left;
else
cur = cur->_right;
}
return nullptr;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
Node* Insert(const T& data)
{
// 先按照二叉搜索树的方式插入新节点
if (_root == nullptr)
{
_root = new Node(data);
return _root;
}
Node *cur = _root;
Node *parent = nullptr;
while (cur)
{
if (cur->_data == data)
return cur;
else if (cur->_data < data)
{
parent = cur;
cur = cur->_right;
}
else
{
parent = cur;
cur = cur->_left;
}
}
// 记录新插入结点
Node *newNode = new Node(data);
// 判断新结点是其父结点的左孩子结点还是右孩子结点, 并插入树中
cur = newNode;
if (cur->_data > parent->_data)
{
parent->_right = cur;
}
else if (cur->_data < parent->_data)
{
parent->_left = cur;
}
cur->_parent = parent;
// 新插入结点后需要 更新平衡因子
// 新增节点的祖先平衡因子会发生变化
// 新增结点为 parent 的左孩子结点 parent的平衡因子 -1;
// 新增节点为 parent 的右孩子结点 parent的平衡因子 +1;
while (parent)
{
// 当前结点新增左节点, 平衡因子 -1;
if (cur == parent->_left)
parent->_bf -= 1;
// 当前结点新增右节点, 平衡因子 +1;
else if (cur == parent->_right)
parent->_bf += 1;
// 如果更新后, parent 的平衡因子 为 0 , 说明 parent 未更新前平衡因子为 1/-1 , 新插入结点使 parnet 左右子树高度相同, 不需要向上更新
if (parent->_bf == 0)
break;
// 如果更新后, parent 的平衡因子为 1 或者 -1, 说明以 parent 为根的左右子树发生了变化, 从而会影响祖先的平衡因子, 需要向上继续更新
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = cur->_parent;
parent = parent->_parent;
}
// 如果更新后, parent 的平衡因子为 2/-2, 说明以 parent 为根的左右子树高度已经不平衡, 需要进行旋转处理
else if (parent->_bf == 2 || parent->_bf == -2)
{
// 旋转分为三种情况: 左单旋, 右单旋, 双旋(又分为 先左旋后右旋, 先右旋后左旋)
if (parent->_bf == 2 && cur->_bf == 1)
{
// 左单旋
LRotate(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
// 右单旋
RRotate(parent);
}
// 双旋的情况需要对平衡因子进行调整操作
else if (parent->_bf == 2 && cur->_bf == -1)
{
int bf = cur->_left->_bf;
// 先对cur进行右旋, 在对 parent 进行左旋
RRotate(cur);
LRotate(parent);
if (bf == -1)
{
parent->_bf = 0;
cur->_bf = 1;
}
if (bf == 1)
{
parent->_bf = -1;
cur->_bf = 0;
}
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
int bf = cur->_right->_bf;
// 先左旋, 后右旋
LRotate(cur);
RRotate(parent);
if (bf == -1)
{
parent->_bf = 1;
cur->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 1;
cur->_bf = -1;
}
}
else
{
assert(false);
}
break;
}
}
return newNode;
}
// 由于在类外, 无法访问私有成员 _root, 所以在类内进行调用
bool IsBalance()
{
return _IsBalance(_root);
}
private:
// 中序遍历 打印
void _InOrder(Node* root)
{
//Node *root = this->_root;
if (root != nullptr)
{
_InOrder(root->_left);
cout << root->_data << " ";
_InOrder(root->_right);
}
}
// 左单旋
void LRotate(Node* parent)
{
// 记录当前结点prent 的右孩子结点sub 和 右孩子的左孩子结点subL
Node *sub = parent->_right;
Node *subL = sub->_left;
// parent 与 subL 建立联系
parent->_right = subL;
if (subL)
subL->_parent = parent;
// parent 与 sub 建立联系---完成旋转
// 两种情况:
// 1. parent为根---sub变为根, sub的祖先指向 nullptr
// 2. parent不为根---记录parent的根结点parentparent ,并使之与sub建立连接
sub->_left = parent;
if (parent == _root)
{
_root = sub;
sub->_parent = nullptr;
parent->_parent = sub;
}
else
{
Node *parentparent = parent->_parent;
sub->_parent = parentparent;
parent->_parent = sub;
if (parent == parentparent->_left)
parentparent->_left = sub;
else
parentparent->_right = sub;
}
parent->_bf = 0;
sub->_bf = 0;
}
// 右单旋
void RRotate(Node *parent)
{
Node *sub = parent->_left;
Node *subR = sub->_right;
parent->_left = subR;
if (subR)
subR->_parent = parent;
sub->_right = parent;
if (parent == _root)
{
_root = sub;
sub->_parent = nullptr;
parent->_parent = sub;
}
else
{
Node *parentparent = parent->_parent;
sub->_parent = parentparent;
parent->_parent = sub;
if (parent == parentparent->_left)
parentparent->_left = sub;
else
parentparent->_right = sub;
}
parent->_bf = 0;
sub->_bf = 0;
}
// 计算以 root 为根 树的高度
int Highly(Node *root)
{
if (root == nullptr)
return 0;
int lefthigh = Highly(root->_left);
int righthigh = Highly(root->_right);
if (lefthigh > righthigh)
return lefthigh + 1;
else
return righthigh + 1;
}
bool _IsBalance(Node *root)
{
if (root == nullptr)
return true;
int lefthigh = Highly(root->_left);
int righthigh = Highly(root->_right);
int diff = righthigh - lefthigh;
if (diff != root->_bf)
{
cout << "结点" << root->_data << "左右子树高度差:" << diff << "平衡因子:" << root->_bf << endl;
return false;
}
return abs(diff < 2) && _IsBalance(root->_left) && _IsBalance(root->_right);
}
};
// 测试程序
void Test()
{
AVLTree<int> t;
// 会发生特殊双旋的两组数据
int a[] = { 30, 20, 60, 10, 25, 50, 80, 5, 15, 12};
int b[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
for (auto &e : a)
{
t.Insert(e);
}
t.Insert(5);
if (t.Find(99) != nullptr)
cout << "找到了" << endl;
t.InOrder();
cout << t.IsBalance();
}