前面对map/multimap/set/multiset进行了简单的介绍,在其文档介绍中发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
1、它的左右子树都是AVL树
2、左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
简单来说,AVL树的任何一个节点作为根节点来看的话,它左右子树的最高层数和最低层数差值只能小于等于1
如果一棵二叉搜索树是高度平衡的,它就是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树的实现只学习树的插入,不学习删除等其他操作,因为AVL树的插入就包含了重要内容(旋转和平衡因子),我们学会插入就足够了,以后面试一般只会让你说一下AVL树的旋转是怎么旋转的,代码基本上不会让你写,因为一是费时间,二是你写的hr一时半会也看不出细节有什么问题。再退一步来讲,hr就算要你写代码也顶天让你写个插入,这一点就足以看出我们对于平衡搜索二叉树的理解了
我们这里就用前面的KV键值对来实现(其实大概率只是使用K值)
template <class K, class V>
class AVLTreeNode
{
public:
pair<K, V> _kv;
AVLTreeNode<K, V>* _left;//左节点
AVLTreeNode<K, V>* _right;//右节点
AVLTreeNode<K, V>* _parent;//父节点
int _bf;//平衡因子
//这里采用右插入平衡因子+1;左插入平衡因子-1
//初始化
AVLTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _bf(0)
{}
};
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:
1. 按照二叉搜索树的方式插入新节点
2. 调整节点的平衡因子
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = nullptr;//parent是cur的父节点
Node* cur = _root;//cur往下走
while (cur)
{
if (cur->_kv.first > kv.first)//我比你小,往左找
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first)//我比你大,往右找
{
parent = cur;
cur = cur->_right;
}
else
{
return false;//不存在找到,因为AVL树不允许有重复值
}
}
走到这里就表示找到我们要插入kv值的正确位置了
cur = new Node(kv);
if (parent->_kv.first < kv.first)//如果new的节点比父节点大,那么父节点的右指针指向new节点
{
parent->_right = cur;
cur->_parent = parent;
}
else//如果new的节点比父节点小,那么父节点的左指针指向new节点
{
parent->_left = cur;
cur->_parent = parent;
}
开始更新平衡因子
while (parent)//更新到根节点才算更新完平衡因子
{
//1、如果是右子树新增,那么父节点的_bf就加一
//2、如果是左子树新增,那么父节点的_bf就减一
//+1和-1大家可以自己决定,只要是对的,怎么都行!
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
// 是否继续更新依据:子树的高度是否变化
// 1、parent->_bf == 0说明之前parent->_bf是 1 或者 -1
// 说明之前parent一边高一边低,这次插入填上矮的那边,parent所在子树高度不变,不需要继续往上更新
// 2、parent->_bf == 1 或 -1 说明之前是parent->_bf == 0,两边一样高,现在插入一边更高了,
// parent所在子树高度变了,继续往上更新
// 3、parent->_bf == 2 或 -2,说明之前parent->_bf == 1 或者 -1,现在插入严重不平衡,违反规则
// 就地处理--旋转
// 旋转:
// 1、让这颗子树左右高度不超过1
// 2、旋转过程中继续保持他是搜索树
// 3、更新调整孩子节点的平衡因子
// 4、让这颗子树的高度跟插入前保持一致
//如果新增节点cur,使得父节点parent的平衡因子变成了0,那么表示该插入节点对整棵树的平衡因子没有影响
//不用向上判断,可以直接退出
if (parent->_bf == 0)
{
break;
}
//如果新增cur使得父节点parent的平衡因子变成了1或者-1,那么我们要继续向上判断是否对上面的节点的
//平衡因子产生了影响
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
这里就表示树结构出问题了,不再是一颗AVL树了,我们就要开始旋转!!!
else if (parent->_bf == 2 || 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;//旋转完一次就可以退出了,因为旋转的时候我们已经向上判断了,除非新插入,否则树就是avl树
}
else
{
assert(false);//这里直接报错,走到这里树就已经不是AVL树了
}
}
return true;
}
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
1. 新节点插入较高左子树的左侧—左左:右单旋
2. 新节点插入较高右子树的右侧—右右:左单旋
3. 新节点插入较高左子树的右侧—左右:先左单旋再右单旋
将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新。
接下来是代码实现:
void RotateL(Node* parent)//左旋
{
Node* subr = parent->_right;
Node* subrl = subr->_left;
parent->_right = subrl;
if (subrl)//上面的节点不可能为空,但是这里的subrl可能为空
{
subrl->_parent = parent;
}
这里要一直向上判断树是不是avl树,因为这个parent可能是根节点,可能不是
如果不是根节点,那么就要继续向上面判断,是否需要旋转,知道整棵树都判断完毕
Node* ppnode = parent->_parent;//记录父节点的父节点,如果为空,表示树不需要旋转了,否则继续向上找
subr->_left = parent;
parent->_parent = subr;
if (ppnode == nullptr)//如果
{
_root = subr;
_root->_parent = nullptr;//这里要置空
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subr;
}
else
{
ppnode->_right = subr;
}
subr->_parent = ppnode;
}
parent->_bf = subr->_bf = 0;//更新平衡因子,左旋之后,parent和subr的左右子树节点数是一样的!
}
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 (ppnode == nullptr)
{
_root = subl;
subl->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subl;
}
else
{
ppnode->_right = subl;
}
subl->_parent = ppnode;
}
parent->_bf = subl->_bf = 0;
}
void RotateLR(Node* parent)//左右双旋
{
Node* subl = parent->_left;
Node* sublr = subl->_right;
int bf = sublr->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == -1)//sublr左子树新增
{
subl->_bf = 0;
parent->_bf = 1;
sublr->_bf = 0;
}
else if (bf == 1)//sublr右子树新增
{
subl->_bf = -1;
parent->_bf = 0;
sublr->_bf = 0;
}
else if (bf == 0)//sublr自己是新增
{
subl->_bf = 0;
parent->_bf = 0;
sublr->_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);
if (bf == -1)//sublr左子树新增
{
subr->_bf = 1;
parent->_bf = 0;
subrl->_bf = 0;
}
else if (bf == 1)//sublr右子树新增
{
subr->_bf = 0;
parent->_bf = -1;
subrl->_bf = 0;
}
else if (bf == 0)//sublr自己是新增
{
subr->_bf = 0;
parent->_bf = 0;
subrl->_bf = 0;
}
else
{
assert(false);
}
}
总结:
假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑
当pSubR的平衡因子为1时,执行左单旋
当pSubR的平衡因子为-1时,执行右左双旋
当pSubL的平衡因子为-1是,执行右单旋
当pSubL的平衡因子为1时,执行左右双旋
旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树验证其为平衡树
每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
节点的平衡因子是否计算正确
int Height(Node* root)//树高
{
if (root == nullptr)
{
return 0;
}
int lefttree = Height(root->_left);
int righttree = Height(root->_right);
return lefttree > righttree ? lefttree + 1 : righttree + 1;
}
bool IsBalance()//判断是否为AVL树
{
return _Balance(_root);
}
bool _Balance(Node* root)判断是否为AVL树
{
if (root == nullptr)
{
return true;
}
int leftheight = Height(root->_left);
int rightheight = Height(root->_right);
if (rightheight - leftheight != root->_bf)
{
cout << root->_kv.first << "平衡因子异常!!!" << endl;
return false;
}
return abs(rightheight - leftheight) < 2 && _Balance(root->_left) && _Balance(root->_right);
}
void Inorder()
//遍历要用到递归,这里我们不能使用_root,因为_root是private成员,所以类外面直接使用_Inorder函数不行
{
_Inorder(_root);
}
void _Inorder(Node* root)//中序遍历
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_Inorder(root->_right);
}
因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。具体实现可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版。
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
using namespace std;
template <class K, class V>
class 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)
{
if (_root == nullptr)
{
_root = new Node(kv);
return true;
}
Node* parent = nullptr;//parent是cur的父节点
Node* cur = _root;//cur往下走
while (cur)
{
if (cur->_kv.first > kv.first)//我比你小,往左找
{
parent = cur;
cur = cur->_left;
}
else if (cur->_kv.first < kv.first)//我比你大,往右找
{
parent = cur;
cur = cur->_right;
}
else
{
return false;//不存在找到,因为AVL树不允许有重复值
}
}
走到这里就表示找到我们要插入kv值的正确位置了
cur = new Node(kv);
if (parent->_kv.first < kv.first)//如果new的节点比父节点大,那么父节点的右指针指向new节点
{
parent->_right = cur;
cur->_parent = parent;
}
else//如果new的节点比父节点小,那么父节点的左指针指向new节点
{
parent->_left = cur;
cur->_parent = parent;
}
开始更新平衡因子
while (parent)//更新到根节点才算更新完平衡因子
{
//1、如果是右子树新增,那么父节点的_bf就加一
//2、如果是左子树新增,那么父节点的_bf就减一
//+1和-1大家可以自己决定,只要是对的,怎么都行!
if (cur == parent->_right)
{
parent->_bf++;
}
else
{
parent->_bf--;
}
// 是否继续更新依据:子树的高度是否变化
// 1、parent->_bf == 0说明之前parent->_bf是 1 或者 -1
// 说明之前parent一边高一边低,这次插入填上矮的那边,parent所在子树高度不变,不需要继续往上更新
// 2、parent->_bf == 1 或 -1 说明之前是parent->_bf == 0,两边一样高,现在插入一边更高了,
// parent所在子树高度变了,继续往上更新
// 3、parent->_bf == 2 或 -2,说明之前parent->_bf == 1 或者 -1,现在插入严重不平衡,违反规则
// 就地处理--旋转
// 旋转:
// 1、让这颗子树左右高度不超过1
// 2、旋转过程中继续保持他是搜索树
// 3、更新调整孩子节点的平衡因子
// 4、让这颗子树的高度跟插入前保持一致
//如果新增节点cur,使得父节点parent的平衡因子变成了0,那么表示该插入节点对整棵树的平衡因子没有影响
//不用向上判断,可以直接退出
if (parent->_bf == 0)
{
break;
}
//如果新增cur使得父节点parent的平衡因子变成了1或者-1,那么我们要继续向上判断是否对上面的节点的
//平衡因子产生了影响
else if (parent->_bf == 1 || parent->_bf == -1)
{
cur = parent;
parent = parent->_parent;
}
这里就表示树结构出问题了,不再是一颗AVL树了,我们就要开始旋转!!!
else if (parent->_bf == 2 || 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;//旋转完一次就可以退出了,因为旋转的时候我们已经向上判断了,除非新插入,否则树就是avl树
}
else
{
assert(false);//这里直接报错,走到这里树就已经不是AVL树了
}
}
return true;
}
void RotateL(Node* parent)
{
Node* subr = parent->_right;
Node* subrl = subr->_left;
parent->_right = subrl;
if (subrl)//上面的节点不可能为空,但是这里的subrl可能为空
{
subrl->_parent = parent;
}
这里要一直向上判断树是不是avl树,因为这个parent可能是根节点,可能不是
如果不是根节点,那么就要继续向上面判断,是否需要旋转,知道整棵树都判断完毕
Node* ppnode = parent->_parent;//记录父节点的父节点,如果为空,表示树不需要旋转了,否则继续向上找
subr->_left = parent;
parent->_parent = subr;
if (ppnode == nullptr)//如果
{
_root = subr;
_root->_parent = nullptr;//这里要置空
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subr;
}
else
{
ppnode->_right = subr;
}
subr->_parent = ppnode;
}
parent->_bf = subr->_bf = 0;//更新平衡因子,左旋之后,parent和subr的左右子树节点数是一样的!
}
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 (ppnode == nullptr)
{
_root = subl;
subl->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = subl;
}
else
{
ppnode->_right = subl;
}
subl->_parent = ppnode;
}
parent->_bf = subl->_bf = 0;
}
void RotateLR(Node* parent)
{
Node* subl = parent->_left;
Node* sublr = subl->_right;
int bf = sublr->_bf;
RotateL(parent->_left);
RotateR(parent);
if (bf == -1)//sublr左子树新增
{
subl->_bf = 0;
parent->_bf = 1;
sublr->_bf = 0;
}
else if (bf == 1)//sublr右子树新增
{
subl->_bf = -1;
parent->_bf = 0;
sublr->_bf = 0;
}
else if (bf == 0)//sublr自己是新增
{
subl->_bf = 0;
parent->_bf = 0;
sublr->_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);
if (bf == -1)//sublr左子树新增
{
subr->_bf = 1;
parent->_bf = 0;
subrl->_bf = 0;
}
else if (bf == 1)//sublr右子树新增
{
subr->_bf = 0;
parent->_bf = -1;
subrl->_bf = 0;
}
else if (bf == 0)//sublr自己是新增
{
subr->_bf = 0;
parent->_bf = 0;
subrl->_bf = 0;
}
else
{
assert(false);
}
}
void Inorder()//中序遍历
{
_Inorder(_root);
}
void _Inorder(Node* root)//中序遍历
{
if (root == nullptr)
{
return;
}
_Inorder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_Inorder(root->_right);
}
int Height(Node* root)//树高
{
if (root == nullptr)
{
return 0;
}
int lefttree = Height(root->_left);
int righttree = Height(root->_right);
return lefttree > righttree ? lefttree + 1 : righttree + 1;
}
bool IsBalance()//判断是否为AVL树
{
return _Balance(_root);
}
bool _Balance(Node* root)判断是否为AVL树
{
if (root == nullptr)
{
return true;
}
int leftheight = Height(root->_left);
int rightheight = Height(root->_right);
if (rightheight - leftheight != root->_bf)
{
cout << root->_kv.first << "平衡因子异常!!!" << endl;
return false;
}
return abs(rightheight - leftheight) < 2 && _Balance(root->_left) && _Balance(root->_right);
}
private:
Node* _root = nullptr;
};
void test()//测试代码
{
//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
int a[] = { 1,2,3 };
//AVLTree q;
//for (auto e : a)
//{
// q.Insert(make_pair(e, e));
//}
//q.Inorder();
//cout << q.IsBalance();
srand(time(0));
const size_t N = 10;
AVLTree<int, int> t;
for (size_t i = 0; i < N; ++i)
{
size_t x = rand();
t.Insert(make_pair(x, x));
//cout << t.IsBalance() << endl;
}
//t.Inorder();
cout << t.IsBalance() << endl;
}