之前我们学习了二叉树的升级版本——搜索二叉树,今天我们要在这个搜索二叉树的基础上在此升级——我们来看看AVL树
AVL树简介
我们之前的二叉搜索树已经大大降低了搜索效率,但是还是没能避免一些极端情况,比如说:
这样的话搜索效率又会变成n方,这可不是什么好兆头,所以有两个俄罗斯的学者发明了一种方法来降低树的高度,并且这两个个学者用自己的名字中的一个字母分别组合起来来命名这种新的数据结构——AVL树。
下面是更为详细的说明:
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查
找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii
和E.M.Landis在1962年
发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右
子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均
搜索长度。
AVL树结点
为了保证每个结点的左右高度差的绝对值不超过1,我们要引入一个平衡因子的概念,就是每个结点都存储着自身的平衡信息。这个平衡因子计算方法是右子树的高度减去左子树的高度。
同时我们会向这棵树中插入结点,这时候,平衡因子肯定会有相应的变化,我们还要有一个指针保存结点的父亲,所以搜索二叉树的结点是这样定义的:
template <class K,class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _leftchild;
AVLTreeNode<K, V>* _rightchild;
AVLTreeNode<K, V>* _parent; //结点的父亲
int _bf; //平衡因子
pair<K, V> _kv; //存储值
};
我们先把大的框架搭好:
template <class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> _Node;
public:
private:
_Node* root = nullptr;
};
AVL的插入
我们首先把AVL树当做一棵普通的搜索二叉树来看,搜索二叉树进行插入我们之前是了然于胸的:
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;
_Node* cur = _root;
//找到一个空位
while (cur)
{
//插入的值比这个结点的值要大
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_rightchild; //往右走
}
//插入的值比这个结点的值要小
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_leftchild; //往左走
}
else //如果相等
{
return false;
}
}
//经过上面的循环,找到了空位,可以插入了
cur = new _Node(kv);
//连接
if (parent->_kv.first > kv.first)
{
parent->_leftchild = cur;
cur->_parent = parent; //同时指向自己的父亲
}
else
{
parent->_rightchild = cur;
cur->_parent = parent;
}
return true;
}
private:
_Node* root = nullptr;
};
此时我们要做的只有一件事,就是我们插入了结点之后,我们的平衡因子是需要更新的,但是这有一个问题,我们平衡因子的更新会有连带效应吗?
假设我们有这样的一棵树:
这个时候我们有不同的插入位置:
第一种:
这个时候除了父节点9的平衡因子要改,上面的组先的平衡因子统统都要改:
但是如果我是在这里插入:
我就只需要修改父节点的平衡因子:
这里我们可以总结出来一个点:
- 如果我们插入结点之后导致树的整体高度改变了,那我们就要依次修改从父节点及其以上的平衡因子。
- 如果插入结点之后并没有改变树的高度,那我们只需要改变父节点的平衡因子。
那么如何判断我们插入的结点改变了树的高度呢?
- 如果插入之后,父节点的平衡因子变为了0,表示我们树的高度没有发生变化。这时候我们只需要修改父节点的平衡因子就行了。
- 如果插入之后,父节点的平衡因子变为了1或者-1,表示我们树的高度发生了变化。这个时候我们就要依次修改从父节点及其以上的平衡因子。
//修改平衡因子
while (parent) //修改到根截止
{
if (cur == parent->_leftchild)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0)
{
break; //高度没有改变,直接退出
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
//往上修改祖先的平衡因子
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//此时树已失去平衡,需要进行旋转来进行平衡
}
}
整体代码如下:
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;
_Node* cur = _root;
//找到一个空位
while (cur)
{
//插入的值比这个结点的值要大
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_rightchild; //往右走
}
//插入的值比这个结点的值要小
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_leftchild; //往左走
}
else //如果相等
{
return false;
}
}
//经过上面的循环,找到了空位,可以插入了
cur = new _Node(kv);
//连接
if (parent->_kv.first > kv.first)
{
parent->_leftchild = cur;
cur->_parent = parent; //同时指向自己的父亲
}
else
{
parent->_rightchild = cur;
cur->_parent = parent;
}
//修改平衡因子
while (parent) //修改到根截止
{
if (cur == parent->_leftchild)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0)
{
break; //高度没有改变,直接退出
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
//往上修改祖先的平衡因子
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//此时树已失去平衡,需要进行旋转来进行平衡
}
}
return true;
}
private:
_Node* root = nullptr;
};
旋转平衡
右右旋
现在我们要研究的是,这棵树已经不平衡了,我们应该用什么样的方法让这棵树再次平衡,我们先从一个简单情况开始:
这个时候这棵树已经不平衡了我们发现,1的右树太高了,该咋办呢?
既然右树太高了,我们可以将1的右树拎起来:
这个时候再将1作为2的左孩子:
这个时候我们发现,树的高度已经被降下来了,并且新的树也同样是搜索二叉树。
我们再来看一个更复杂的情况:
我们这时候也是发现30的右树高了,我们将30的右树60拎起来:
将60的左孩子变成30的右孩子:
再将30连接到60的左孩子上:
我们将情况一般化一下:
现在我们60的右树插入一个结点:
这时候我们把60拎起来:
把b接到30的右子树上,载把30接到60的左子树上。
我们再来回过头来,用父祖辈关系来阐释我们的结果:
我们发现被插入的树是没有移动位置的,我们只需要改变parent,subR,subRL这几个的父子辈关系和平衡因子:
void RoateR(_Node* parent)
{
_Node* subR = parent->_rightchild;
_Node* subRL = subR->_leftchild;
subR->_leftchild = parent;
parent->_rightchild = subRL;
}
这段代码看起来没有什么问题,但是这段代码只改变了子孙关系,但是没有改变父亲关系,如果没有改变父亲关系,就会出岔子:
void RoateRR(_Node* parent) //左旋
{
_Node* subR = parent->_rightchild;
_Node* subRL = subR->_leftchild;
subR->_leftchild = parent;
parent->_rightchild = subRL;
//修改父辈指向
parent->_parent = subR;
if (subRL) //subRL可能为空
subRL->_parent = parent;
但是这个时候subR的父亲还是指向的是parent,这下我们要分两种情况:
如果此时的subR就是_root的位置:此时subR取代_root的位置,并且将subR的_parent指向nullptr
if (_root == subR)
{
_root = subR;
subR->_parent = nullptr;
}
如果我们改变的只是一棵树的一部分,我们也要记录parent的父亲:pparent
接下来我们要做的就是修改平衡因子:
我们发现parent的平衡因子为0,subR的平衡因子也为0:
parent->_bf = subR->_bf = 0;
这个时候我们才完成一次右右旋(在这里,因为是在右树右孩子插入的,我把这种的旋转叫做右右旋)
void RoateRR(_Node* parent) //右右旋
{
_Node* subR = parent->_rightchild;
_Node* subRL = subR->_leftchild;
subR->_leftchild = parent;
parent->_rightchild = subRL;
_Node* pparent = parent->_parent;
//修改父辈指向
parent->_parent = subR;
if (subRL) //subRL可能为空
subRL->_parent = parent;
if (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (pparent->_leftchild == parent)
{
pparent->_leftchild = subR;
}
else
{
pparent->_rightchild = subR;
}
//修改subR的父亲关系
subR->_parent = pparent;
}
parent->_bf = subR->_bf = 0;
}
左左旋
左左旋和右右旋正好相反:
将3的左树提起来
再将3接到2的右树上:
我们再将情况一般化一下:
我们依葫芦画瓢的写一下左左旋:
void RoateLL(_Node* parent)
{
_Node* subL = parent->_leftchild;
_Node* subLR = subL->_rightchild;
//修改孙子辈分
subL->_rightchild = parent;
parent->_leftchild = subRL;
//记录pparent
_Node* pparent = parent->_parent;
//修改父辈指向
parent->_parent = subL;
if (subLR)
subLR->_parent = parent;
if (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (pparent->_leftchild = parent)
{
pparent->_leftchild = subL;
}
else
{
pparent->_rightchild = subL;
}
subL->_parent = pparent;
}
subL->_bf = parent->_bf = 0;
}
我们再来分辨一下,什么时候是右右旋,什么时候是左左旋:
当parent的平衡因子为2和subR的平衡因子为1时,进行右右旋。
else if (parent->_bf == 2 || parent->_bf == -2)
{
//此时树已失去平衡,需要进行旋转来进行平衡
if (parent->_bf == 2 && cur->_bf == 1)
{
RoateRR(parent);
}
}
当parent的平衡因子为-2和subR的平衡因子为1-时,进行左左旋。
else if (parent->_bf == 2 || parent->_bf == -2)
{
//此时树已失去平衡,需要进行旋转来进行平衡
if (parent->_bf == 2 && cur->_bf == 1)
{
RoateRR(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RoateLL(parent);
}
}
左左旋+右右旋
我们现在来看另外一个情况:就是我们是右树高,但是不是右右高,是右左高。
如果我们是在subR的左子树插入,我们右右旋就没办法解决问题了,我们不妨把subRL的一个结点拿出来:
这个时候我们可能在subRL的左孩子或者右孩子插入,我们先在subRL的右子树插入一个结点:
这个时候站在subR的角度来看,subR的左树高,先对subR进行左左旋:
这个时候站在parent的角度上是右树高了,要进行右右旋:
我们推导出这个这个结论是subRL的右树插入的:
如果我们是在subRL的左树插入,我们也应该推导的出:
右右旋+左左旋
同样的我们可以推出左树高但是在左树的右子树插入:
附上源码:
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
template <class K,class V>
struct AVLTreeNode
{
AVLTreeNode<K, V>* _leftchild;
AVLTreeNode<K, V>* _rightchild;
AVLTreeNode<K, V>* _parent; //结点的父亲
int _bf; //平衡因子
pair<K, V> _kv; //存储值
AVLTreeNode(const pair<K,V>& kv)
:_leftchild(nullptr)
,_rightchild(nullptr)
,_parent(nullptr)
,_kv(kv)
,_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;
_Node* cur = _root;
//找到一个空位
while (cur)
{
//插入的值比这个结点的值要大
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_rightchild; //往右走
}
//插入的值比这个结点的值要小
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_leftchild; //往左走
}
else //如果相等
{
return false;
}
}
//经过上面的循环,找到了空位,可以插入了
cur = new _Node(kv);
//连接
if (parent->_kv.first > kv.first)
{
parent->_leftchild = cur;
cur->_parent = parent; //同时指向自己的父亲
}
else
{
parent->_rightchild = cur;
cur->_parent = parent;
}
//修改平衡因子
while (parent) //修改到根截止
{
if (cur == parent->_leftchild)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0)
{
break; //高度没有改变,直接退出
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
//往上修改祖先的平衡因子
cur = parent;
parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
//此时树已失去平衡,需要进行旋转来进行平衡
if (parent->_bf == 2 && cur->_bf == 1)
{
RoateRR(parent);
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
RoateLL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
RoateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
RoateRL(parent);
}
break;
}
else
{
assert(false);
}
}
return true;
}
void RoateRR(_Node* parent) //右右旋
{
_Node* subR = parent->_rightchild;
_Node* subRL = subR->_leftchild;
subR->_leftchild = parent;
parent->_rightchild = subRL;
_Node* pparent = parent->_parent;
//修改父辈指向
parent->_parent = subR;
if (subRL) //subRL可能为空
subRL->_parent = parent;
if (_root == parent)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (pparent->_leftchild == parent)
{
pparent->_leftchild = subR;
}
else
{
pparent->_rightchild = subR;
}
//修改subR的父亲关系
subR->_parent = pparent;
}
parent->_bf = subR->_bf = 0;
}
void RoateLL(_Node* parent)
{
_Node* subL = parent->_leftchild;
_Node* subLR = subL->_rightchild;
//修改孙子辈分
subL->_rightchild = parent;
parent->_leftchild = subLR;
//记录pparent
_Node* pparent = parent->_parent;
//修改父辈指向
parent->_parent = subL;
if (subLR)
subLR->_parent = parent;
if (_root == parent)
{
_root = subL;
subL->_parent = nullptr;
}
else
{
if (pparent->_leftchild == parent)
{
pparent->_leftchild = subL;
}
else
{
pparent->_rightchild = subL;
}
subL->_parent = pparent;
}
subL->_bf = parent->_bf = 0;
}
void RoateRL(_Node* parent)
{
_Node* subR = parent->_rightchild;
_Node* subRL = subR->_leftchild;
int bf = subRL->_bf;
//先进行左左旋
RoateLL(parent->_rightchild);
//再进行右右旋
RoateRR(parent);
//修改平衡因子
if (bf == 0)
{
parent->_bf = subR->_bf = subRL->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 0;
subRL->_bf = 0;
subR->_bf = 1;
}
else if (bf == 1)
{
parent->_bf = -1;
subRL->_bf = 0;
subR->_bf = 0;
}
else
{
assert(false);
}
}
void RoateLR(_Node* parent)
{
_Node* subL = parent->_leftchild;
_Node* subLR = subL->_rightchild;
int bf = subLR->_bf;
//先进行右右旋
RoateRR(parent->_leftchild);
//再进行左左旋
RoateLL(parent);
if (bf == 0)
{
parent->_bf = subL->_bf = subLR->_bf = 0;
}
else if (bf == 1)
{
parent->_bf = 0;
subL->_bf = - 1;
subLR->_bf = 0;
}
else if (bf == -1)
{
parent->_bf = 1;
subL->_bf = 0;
subLR->_bf = 0;
}
else
{
assert(false);
}
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
void _InOrder(_Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_leftchild);
cout << root->_kv.first << " ";
_InOrder(root->_rightchild);
}
bool IsBalance()
{
return _IsBalance(_root);
}
int Height()
{
return _Height(_root);
}
int _Height(_Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->_leftchild);
int rightHeight = _Height(root->_rightchild);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalance(_Node* root)
{
if (root == nullptr)
return true;
int leftHeight = _Height(root->_leftchild);
int rightHeight = _Height(root->_rightchild);
if (rightHeight - leftHeight != root->_bf)
{
cout << root->_kv.first << "平衡因子异常" << endl;
return false;
}
return abs(rightHeight - leftHeight) < 2
&& _IsBalance(root->_leftchild)
&& _IsBalance(root->_rightchild);
}
private:
_Node* _root = nullptr;
};
#define _CRT_SECURE_NO_WARNINGS 1
#include<vector>
#include"AVLTree.h"
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> t;
//for (auto e : a)
//{
// t.Insert(make_pair(e,e));
//}
//t.InOrder();
//cout << t.IsBalance() << endl;
const int N = 600000;
vector<int> v;
v.reserve(N);
srand(time(0));
for (size_t i = 0; i < N; i++)
{
v.push_back(rand());
cout << v.back() << endl;
}
AVLTree<int, int> t;
for (auto e : v)
{
t.Insert(make_pair(e, e));
//cout << "Insert:" << e << "->" << t.IsBalance() << endl;
}
cout << t.IsBalance() << endl;
cout << t.Height() << endl;
}