一、本博客主要内容:
1、avl平衡树的概念,性质
2、avl平衡树插入节点方式图解
3、avl平衡树两种实现方式+代码讲解
(1)、平衡树节点结构
(2)、插入节点
(3)、旋转节点
(4)、查找节点
(5)、删除节点
(6)、平衡判断
4、完整代码链接
二、平衡树的性质:
1、平衡树是一个高度平衡的二叉搜索树,左右子树的高度差不超过1,并且左右子书都是平衡树
2、平衡树满足二叉搜索树的性质:左子树节点 < 根节点 < 右子树
3、平衡树的查找,插入,删除效率为O(lgN)
4、avl树是一个严格的平衡树,插入和删除操作需大量的旋转调平操作,以及平衡因子的计算处理,所以效率没有红黑树的效率高,应用也无红黑树多。
三、avl树插入节点图解:
四、avl树操作代码讲解
avl树实现方法1:(递归实现)
1、节点结构:
struct AVLNode {
int _key;
int _value;
int _hr; //表示树的高度
AVLNode* _left;
AVLNode* _right;
AVLNode(int key = 0, int value = 0)
:_key(key)
, _value(value)
, _hr(1) //高度从1开始
, _left(NULL)
, _right(NULL)
{}
};
2、获取树的高度及更新操作
int getHr(Node* node) {
if (node) {
return node->_hr;
}
return 0;
}
//树的高度=左右子树高度较大的+1(根节点)
void upHr(Node* &node) {
if (node) {
int lhr = getHr(node->_left);
int rhr = getHr(node->_right);
node->_hr = (lhr > rhr? lhr : rhr) + 1; //树的高度为较高的高度值+1
}
}
3、插入节点
方法:
1、判断节点是否为空,空则返回一个新节点(new);
2、先递归查找插入节点的位置(注:要接受返回的节点)
3、更新节点的hr
4、根据计算左右子树的hr差值,选择对应的旋转方式。
5、返回当前处理的节点,依据递归的回退,继续处理上层节点(循环2-4)
void put(int key, int value) {
_root = _put(_root, key, value);
}
//递归插入节点
Node* _put(Node* root, int key, int value) {
if (root == NULL) {
return new Node(key,value);
}
//查找插入位置
if (root->_key > key) {
root->_left = _put(root->_left, key, value);
}
else if (root->_key < key) {
root->_right = _put(root->_right, key, value);
}
else { //已存在key,更新value
root->_value = value;
}
//递归更新每个节点的高和旋转调平
//1、更新树的高度
upHr(root);
//2、旋转节点
if (getHr(root->_right)-getHr(root->_left) == -2) { //左子树比右子树高超过1
Node* child = root->_left;
if (getHr(child->_right)-getHr(child->_left) == 1) { //双旋
root->_left = rotateLeft(child); //左旋
}
root = rotateRight(root);//右旋
}
if (getHr(root->_right) - getHr(root->_left) == 2) { //右子树比左子树高超过1
Node* child = root->_right;
if (getHr(child->_right) - getHr(child->_left) == -1) { //双旋
root->_right = rotateRight(child);//右旋
}
root = rotateLeft(root); //左旋
}
return root;
}
4、旋转操作
1、左旋转:
1、交换旋转节点和旋转节点的右子节点
2、将旋转节点的右子节点的左孩子挂在旋转节点的右侧
3、更新旋转节点和旋转节点的右孩子的hr值
4、返回旋转节点的右子节点(作为新的旋转节点位置的节点)
Node* rotateLeft(Node* node) {
Node* node_right = node->_right;
Node* node_rleft = node_right->_left;
//交换节点的位置
node_right->_left = node;
node->_right = node_rleft;
//更新旋转两个节点的高
upHr(node);
upHr(node_right);
return node_right;
}
2、右旋转方法与左旋转类似
Node* rotateRight(Node* node) {
Node* node_left = node->_left;
Node* node_lright = node_left->_right;
//交换节点的位置
node_left->_right = node;
node->_left = node_lright;
//更新节点的高
upHr(node);
upHr(node_left);
return node_left;
}
6、删除节点
删除节点方式与二叉搜索树操作类似,只是多了删除后重新计算hr值和旋转调平操作
1、节点为空,返回NULL
2、递归查找删除节点(注:接收返回值)
3、找到删除节点:
(1)、删除节点的左为空,删除该节点并返回节点的右孩子
(2)、删除节点的右为空,删除该节点并返回节点的左孩子
(3)、查找替换节点(在节点的左子树找最大值,或右子树找最小值),交换删除节点和替换节点的值(value和key),继续递归删除替换节点(节点的右孩子/左孩子,原key节点)。返回替换节点。
4、更新节点的hr
5、计算hr进行旋转调平
//删除节点
void erase(int key) {
_root = _erase(_root, key);
}
Node* _erase(Node* root, int key) {
if (root == NULL) return NULL;
if (root->_key > key) {
root->_left = _erase(root->_left, key);
}
else if (root->_key < key) {
root->_right = _erase(root->_right, key);
}
else {
Node* del = root;
if (del->_left == NULL) {
root = del->_right;
delete del;
return root;
}
if (del->_right == NULL) {
root = del->_left;
delete del;
return root;
}
//1、寻找替换节点,删除替换节点
Node* min_right = getMin(root->_right);
swap(root->_key, min_right->_key);
swap(root->_value, min_right->_value);
root->_right = _erase(root->_right, key); //在替换节点位置去删除该节点
return root; //返回替换节点
}
//平衡处理
//1、重新计算hr
upHr(root);
//2、旋转处理
if (getHr(root->_right) - getHr(root->_left) == -2) { //左子树比右子树高超过1
Node* child = root->_left;
if (getHr(child->_right) - getHr(child->_left) == 1) { //双旋
root->_left = rotateLeft(child); //左旋
}
root = rotateRight(root);//右旋
}
if (getHr(root->_right) - getHr(root->_left) == 2) { //右子树比左子树高超过1
Node* child = root->_right;
if (getHr(child->_right) - getHr(child->_left) == -1) { //双旋
root->_right = rotateRight(child);//右旋
}
root = rotateLeft(root); //左旋
}
return root;
}
6、平衡判断
1、通过计算hr的差值判断是否平衡
//平衡判断
bool isBlance() {
return _isBlance(_root);
}
bool _isBlance(Node* root) {
if (root == NULL) {
return true;
}
int left_hr = getHr(root->_left);
int right_hr = getHr(root->_right);
if (right_hr - left_hr > -2 && right_hr - left_hr < 2) { //根据左右子树的高度差判断
return true;
}
return _isBlance(root->_left) && _isBlance(root->_right);
}
7、查找节点(key)
int get(int key) {
Node* node = _get(_root, key);
if (node) {
return node->_value;
}
return -1; //未找到返回-1
}
Node* _get(Node* root, int key) {
if (root == NULL) return NULL;
if (root->_key > key) {
return _get(root->_left, key);
}
else if(root->_key < key){
return _get(root->_right, key);
}else{
return root;
}
}
平衡树的其它操作与二叉搜索树操作类,此处不在重述,可以看二叉查找(搜索)树的操作实现即可。
五、avl树实现方法2(非递归):
1、节点结构:
template<class T,class V>
struct AvlNode
{
T _key;
V _value;
int _bf; //平衡因子
AvlNode<T,V>* _left;
AvlNode<T,V>* _right;
AvlNode<T,V>* _parent;
AvlNode(const T& key,const V& value)
:_key(key)
,_value(value)
,_bf(0) //bf表示平衡因子
,_left(NULL)
,_right(NULL)
,_parent(NULL) //用于回退处理上层操作
{}
};
1、节点插入操做(非递归)
平衡处理:
1、添加在较高右子树的右节点上(右节点的右或左),右比左长—左旋
2、添加在较高左子树的左节点上(左节点的左或右),左比右长—右旋
3、添加在较高右子树的左节点上(左节点的左或右)–右比左长—右,左双旋,先右旋成1种,在进行左旋
4、添加在较高左子树的右节点上(右节点的左或右)–左比右长—左,右双旋,先左旋成2种,在进行右旋
bool Push_Back(const T& Key,const V& value)
{
if(_root == NULL)
{
_root = new Node(Key,value);//根节点
return true;
}
Node* cur = _root;
Node* parent = NULL;
//遍历查找到可插入位置
while(cur)
{
if(cur->_key < Key)
{
parent = cur;
cur = cur->_right;
}
else if(cur->_key > Key)
{
parent = cur;
cur = cur->_left;
}
else//数值相同错误
return false;
}
//链接新节点
cur = new Node(Key,value);
if(parent->_key < Key)//插入在右侧
{
parent->_right = cur;
}
else//插入在左侧
{
parent->_left = cur;
}
cur->_parent = parent;//初始新节点的父亲
//处理平衡因子
while(parent) //循环处理
{
//平衡因子计算(处理双亲(父)节点的因子)
if(parent->_left == cur)//插入在双亲的左
parent->_bf--; //平衡因子减-1
else //插入在双亲的右
parent->_bf++; //平衡因子+1
//根据平衡因子的数值判断是否调整树
if(parent->_bf == 0)//平衡不用调整树,退出
break;
else if(parent->_bf == 1 || parent->_bf == -1)//不平衡,因子为1--向上回退
{
cur = parent;
parent = cur->_parent;
}
//需平衡调整树
else//不平衡,因子为2,平衡处理
{
if(parent->_bf == 2)//树的右侧长
{
if(cur->_bf == -1)//插入在右子树节点左侧
Lotatel(parent);//左单旋
else //插入在右子树节点右侧
Rotaterl(parent);//右左旋
}
else//树的左侧长
{
if(cur->_bf == -1) //插入在右子树节点左侧
Rotatel(parent);//右旋
else //插入在右子树节点左侧
Rotatelr(parent);//左右旋
}
break;
}
}
return true;
}
3、左旋转
void Lotatel(Node* parent)//左旋---父节点的右长
{
//parent是因子值为2的节点
Node* subr = parent->_right;//获取父节点的右子树
Node* subl = subr->_left; //获取右子树的左孩子
//改变指针域,实现树的调整
parent->_right = subl; //父节点的右指向subr的左孩子
if(subl){//右子树存在左孩子
subl->_parent = parent;//更改subr左孩子的父节点
}
subr->_left = parent; //更改subr的左指域指向父节点
Node* pparent = parent->_parent;//获取原父节点的父亲
subr->_parent = pparent;//更新subr父亲节点的父亲
parent->_parent = subr;//subr当作新的父亲节点
if(parent == _root)//父节点为根节点
_root = subr;//更改根节点
else
{
if(pparent->_left == parent)//原父亲节点在左侧
pparent->_left = subr;//新父亲节点连接在左侧(原父亲节点的父亲节点左侧)
else
pparent->_right = subr;////新父亲节点连接在右侧
}
//更新节点因子值
parent->_bf = subr->_bf = 0;
parent->_parent = NULL;
}
4、右旋转
void Rotatel(Node* parent)//右旋
{
Node* subl = parent->_left;
Node* sublr = subl->_right;
parent->_left = sublr;
if(sublr){ //存在父节点的右子树存在左节点
sublr->_parent = parent;//更改其父节点
}
subl->_right = parent;
Node* pparent = parent->_parent;//原父节点的父亲
subl->_parent = pparent;
parent->_parent = subl;
if(parent == _root)//父节点为根节点
_root = subl;//更改根节点
else
{
if(pparent->_left == parent)
pparent->_left = subl;
else
pparent->_right = subl;
}
//更新节点因子
parent->_bf = subl->_bf = 0;
parent->_parent = NULL;
}
5、左,右双旋
void Rotatelr(Node* parent)//左,右双旋
{
Node* subl = parent->_left;
Node* sublr = subl->_right;
int bf = sublr->_bf;//记录旋转之前的因子值
Lotatel(parent->_left);//左旋
Rotatel(parent);//右旋
//更新因子值
if(bf == -1)
subl->_bf = 1;
else if(bf == 1)
parent->_bf = -1;
}
6、右,左双旋
void Rotaterl(Node* parent)//右,左双旋
{
Node* subr = parent->_right;
Node* subrl = subr->_left;
int bf = subrl-> _bf;//记录旋转之前的
Rotatel(parent->_right);//右旋
Lotatel(parent);//左旋
if(bf == 1)
parent->_bf = -1;
else if(bf == -1)
subr->_bf = 1;
}
7、计算树的高度
size_t Height(Node* cur)
{
if(cur == NULL) return 0;
static size_t n1 = 1;//根节点为第一层
static size_t n2 = 1;
n1 = Height(cur->_left);
n2 = Height(cur->_right);
if(cur->_left || cur->_right)//在有左或右子树的节点才进行比较
return (n1 > n2 ? n1 : n2)++;
return 1;//只有根节点
}
六、代码链接
1、avl树实现代码1:https://github.com/weienjun/-algorithm/tree/master/avl
2、avl树实现代码2:https://github.com/weienjun/-algorithm/tree/master/avl2