平衡二叉树
二叉搜索树虽然可以缩短查找的效率,但如果 数据有序或接近有序的二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此就有了解决上述问题的方法:平衡二叉树。
平衡二叉树的概念
平衡二叉树(AVL树):当二叉搜索树插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整)。
一棵AVL树,要么是一棵空树,要么是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树;
- 左右子树高度之差(简称平衡因子)的绝对值不超过1。
AVL树结点的定义
template<class T>
struct AVLTreeNode{
AVLTreeNode(const T& val)
:_left(nullptr), _right(nullptr), _parent(nullptr)
, _data(val), _bf(0)
{}
AVLTreeNode<T>* _left; //该结点的左孩子
AVLTreeNode<T>* _right; //该结点的右孩子
AVLTreeNode<T>* _parent; //该结点的双亲
T _data;
int _bf; //该结点的平衡因子
};
AVL树的插入
以下平衡因子的算法都采用右子树高度-左子树高度
AVL树是在二叉搜索树的基础上引入平衡因子,因此AVL树的插入可以分为两步:
1.按照二叉搜索树的方式插入新结点;
2.调整结点的平衡因子。
如何调整平衡因子
在cur插入前,parent的平衡因子分为三种情况:-1,0,1,分以下两种情况:
1.如果cur插入到parent的左侧,只需要给parent的平衡因子-1;
2.如果cur插入到parent的右侧,只需要给parent的平衡因子+1。
此时,parent的平衡因子可能有三种情况:0,±1,±2,又存在以下三种情况:
1.parent的平衡因子为0,说明插入前parent的平衡因子为±1,插入后被调整为0,满足AVL树性质;
2.parent的平衡因子为±1,说明插入前parent的平衡因子为0,插入后被调整为±1,此时,以parent为根的树的高度增加,需要继续向上更新平衡因子;
3.parent的平衡因子为±2,违反了AVL树的性质,需要进行旋转处理。
左左:右单旋
新结点插入较高左子树的左侧:
void _rotateR(Node* parent){
Node* subL = parent->_left; //父结点左子树的根结点
Node* subLR = subL->_right; //父结点左子树的右子树的根结点
//右旋:更新结点链接情况
subL->_right = parent;
parent->_left = subLR;
if (subLR != nullptr)
subLR->_parent = parent;
if (parent == _root){ //parent是根结点
_root = subL;
subL->_parent = nullptr;
}
else{
Node* pparent = parent->_parent; //记录parent的父结点
if (pparent->_left == parent)
pparent->_left = subL;
else
pparent->_right = subL;
subL->_parent = pparent;
}
parent->_parent = subL;
//更新平衡因子
parent->_bf = subL->_bf = 0;
}
右右:左单旋
新结点插入较高右子树的右侧:
void _rotateL(Node* parent){
Node* subR = parent->_right;
Node* subRL = subR->_left;
subR->_left = parent;
parent->_right = subRL;
if (subRL != nullptr)
subRL->_parent = parent;
if (parent == _root){
_root = subR;
subR->_parent = nullptr;
}
else{
Node* pparent = parent->_parent;
if (pparent->_left == parent)
pparent->_left = subR;
else
pparent->_right = subR;
subR->_parent = pparent;
}
parent->_parent = subR;
parent->_bf = subR->_bf = 0;
}
左右:先左旋,再右旋
新结点插入较高左子树的右侧:
void _rotateLR(Node* parent){
Node* subL = parent->_left;
Node* subLR = subL->_right;
//旋转之前,保留subLR的平衡因子
int bf = subLR->_bf;
//旋转完成后,需要根据该平衡因子调整其他结点的平衡因子
_rotateL(parent->_left);
_rotateR(parent);
//更新平衡因子
if (1 == bf)
subL->_bf = -1;
else if (-1 == bf)
parent->_bf = 1;
}
右左:先右旋,再左旋
新结点插入较高右子树的左侧:
void _rotateRL(Node* parent){
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
_rotateR(parent->_right);
_rotateL(parent);
if (1 == bf)
parent->_bf = -1;
else if (-1 == bf)
subR->_bf = 1;
}
AVL树的验证
验证其为二叉搜索树
如果中序遍历可以得到有序序列,就说明为二叉搜索树。
验证其为平衡树
- 每个结点子树高度差的绝对值不超过1;
- 结点的平衡因子计算是否正确。
bool IsBalance(Node* root){
if (root == nullptr)
return true;
//计算root结点的平衡因子
int leftHeight = _height(root->_left);
int rightHeight = _height(root->_right);
int diffH = rightHeight - leftHeight;
//如果计算出的diffH与root的平衡因子不相等,或者
//diffH的值大于1或小于-1,则不是AVL树
if (diffH != root->_bf || ((diffH > 1) || (diffH < -1)))
return false;
//如果root的左右子树都是AVL树,则该树一定是AVL树
return IsBalance(root->_left) && IsBalance(root->_right);
}
int _height(Node* root){
if (root == nullptr)
return 0;
int leftH = _height(root->_left);
int rightH = _height(root->_right);
return (leftH > rightH) ? (leftH + 1) : (rightH + 1);
}
AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树,这样可以保证查询时高效的时间复杂度(log2N),但如果对AVL树做一些结构修改的操作,性能非常低下。如果需要一种查询高效且有序的数据结构,而且数据个数不会轻易改变,可以考虑AVL树。
AVL树的实现
#include <iostream>
using namespace std;
template<class T>
struct AVLTreeNode{
AVLTreeNode(const T& val)
:_left(nullptr), _right(nullptr), _parent(nullptr)
, _data(val), _bf(0)
{}
AVLTreeNode<T>* _left; //该结点的左孩子
AVLTreeNode<T>* _right; //该结点的右孩子
AVLTreeNode<T>* _parent; //该结点的双亲
T _data;
int _bf; //该结点的平衡因子
};
template<class T>
class AVLTree{
public:
typedef AVLTreeNode<T> Node;
AVLTree() :_root(nullptr){}
~AVLTree(){
_destroy(_root);
}
bool Insert(const T& val){
//按照二叉搜索树规则插入新结点
if (_root == nullptr){
_root = new Node(val);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur){
parent = cur;
if (cur->_data == val)
return false;
else if (cur->_data > val)
cur = cur->_left;
else
cur = cur->_right;
}
cur = new Node(val);
if (parent->_data > val)
parent->_left = cur;
else
parent->_right = cur;
cur->_parent = parent; //链接parent指针
//调整双亲结点的平衡因子
//更新范围:parent结点到根结点
while (parent){
if (cur == parent->_left) //插入到左子树,左子树高度+1
parent->_bf--;
else if(cur == parent->_right) //插入到右子树,不能用else
parent->_bf++;
//判断parent的平衡因子
if (0 == parent->_bf) //parent结点某子树补齐,parent高度不变
break;
else if (-1 == parent->_bf || 1 == parent->_bf){ //parent子树高度+1,往上更新
cur = parent;
parent = cur->_parent;
}
else{ //调整搜索树重写达到平衡
//左子树的左节点:右旋
if (-2 == parent->_bf && -1 == cur->_bf)
_rotateR(parent);
//右子树的右结点:左旋
else if (2 == parent->_bf && 1 == cur->_bf)
_rotateL(parent);
//左子树的右结点:先左旋,再右旋
else if (-2 == parent->_bf && -1 == cur->_bf)
_rotateLR(parent);
//右子树的左结点:先右旋,再左旋
else if (2 == parent->_bf && -1 == cur->_bf)
_rotateRL(parent);
}
}
return true;
}
void Inorder(){
if (IsBalance(_root))
_inorder(_root);
else
cout << "不是AVL树";
cout << endl;
}
bool IsBalance(Node* root){
if (root == nullptr)
return true;
//计算root结点的平衡因子
int leftHeight = _height(root->_left);
int rightHeight = _height(root->_right);
int diffH = rightHeight - leftHeight;
//如果计算出的diffH与root的平衡因子不相等,或者
//diffH的值大于1或小于-1,则不是AVL树
if (diffH != root->_bf || ((diffH > 1) || (diffH < -1)))
return false;
//如果root的左右子树都是AVL树,则该树一定是AVL树
return IsBalance(root->_left) && IsBalance(root->_right);
}
private:
void _rotateR(Node* parent){
Node* subL = parent->_left; //父结点左子树的根结点
Node* subLR = subL->_right; //父结点左子树的右子树的根结点
//右旋:更新结点链接情况
subL->_right = parent;
parent->_left = subLR;
if (subLR != nullptr)
subLR->_parent = parent;
if (parent == _root){ //parent是根结点
_root = subL;
subL->_parent = nullptr;
}
else{
Node* pparent = parent->_parent; //记录parent的父结点
if (pparent->_left == parent)
pparent->_left = subL;
else
pparent->_right = subL;
subL->_parent = pparent;
}
parent->_parent = subL;
//更新平衡因子
parent->_bf = subL->_bf = 0;
}
void _rotateL(Node* parent){
Node* subR = parent->_right;
Node* subRL = subR->_left;
subR->_left = parent;
parent->_right = subRL;
if (subRL != nullptr)
subRL->_parent = parent;
if (parent == _root){
_root = subR;
subR->_parent = nullptr;
}
else{
Node* pparent = parent->_parent;
if (pparent->_left == parent)
pparent->_left = subR;
else
pparent->_right = subR;
subR->_parent = pparent;
}
parent->_parent = subR;
parent->_bf = subR->_bf = 0;
}
void _rotateLR(Node* parent){
Node* subL = parent->_left;
Node* subLR = subL->_right;
//旋转之前,保留subLR的平衡因子
int bf = subLR->_bf;
//旋转完成后,需要根据该平衡因子调整其他结点的平衡因子
_rotateL(parent->_left);
_rotateR(parent);
//更新平衡因子
if (1 == bf)
subL->_bf = -1;
else if (-1 == bf)
parent->_bf = 1;
}
void _rotateRL(Node* parent){
Node* subR = parent->_right;
Node* subRL = subR->_left;
int bf = subRL->_bf;
_rotateR(parent->_right);
_rotateL(parent);
if (1 == bf)
parent->_bf = -1;
else if (-1 == bf)
subR->_bf = 1;
}
void _destroy(Node*& root){
if (root == nullptr)
return;
_destroy(root->_left);
_destroy(root->_right);
delete root;
root = nullptr;
}
void _inorder(Node* root){
if (root == nullptr)
return;
_inorder(root->_left);
cout << root->_data << " ";
_inorder(root->_right);
}
int _height(Node* root){
if (root == nullptr)
return 0;
int leftH = _height(root->_left);
int rightH = _height(root->_right);
return (leftH > rightH) ? (leftH + 1) : (rightH + 1);
}
private:
Node* _root;
};
感悟
关于感悟是基于自己实现的代码中出现的问题
在判断插入结点是左孩子还是右孩子更新双亲结点的时候,
if (cur == parent->_left) //插入到左子树,左子树高度+1
parent->_bf--;
else //插入到右子树
parent->_bf++;
这是最初的代码,用的是else而不是else if,但是在某些特殊情况的插入时,比如已经有结点1和4,要再插入6时,首先会找到parent和cur,
在cur位置插入6之后会左旋调整,但调整的流程是:移动parent指针,
当parent指针的平衡因子不满足AVL树的性质时进行旋转,于是就变成了下面这样:
这样一来满足了AVL树的性质,但cur变成了parent的父结点,如此在进入上面if else语句中会进入parent->_bf++的语句导致死循环而不是跳出循环。