1.为什么提出AVL树
学习完搜索二叉树以后,我们应该想到一个问题,如果我们的搜索二叉树的趋向于单链的形式,类似于:
2.二叉平衡树概念和结构
为了解决上述问题,所以提出了一个概念,叫做二叉平衡树。
二叉平衡树,相对于二叉搜索树,引入了一个叫做平衡因子的概念。
平衡因子:平衡因子就是右子树的深度-左子树的深度。
二叉平衡树的规则是:每一个节点的平衡因子的绝对值都要小于2。
所以我们需要给一个平衡因子。
二叉平衡树的结构:
template<typename K,typename V>
struct AVLBinaryTreeNode
{
typedef AVLBinaryTreeNode<K, V> Node;
AVLBinaryTreeNode(const K& key,const V& value)
:_left(NULL)
,_right(NULL)
, _parent(NULL)
,_key(key)
, _bf(0)
, _value(value)
{}
Node* _left;
Node* _right;
Node* _parent;
int _bf; //来保存右子树高度-左子树高度,平衡因子。
K _key;
V _value;
};
3.二叉平衡树的平衡化旋转
对于二叉平衡树来说,最重要的就是它的旋转算法,因为他要保证所有节点的平衡因子保持在0,1,-1,所以当平衡因子为2或者-2的时候,我们需要调整,这个时候就引入了旋转这个概念。
例如:
比如上面的这个例子,我们可以看出来在这我们的3已经不满足二叉平衡树的条件了,所以在这里我们应该进行旋转。
另外在这里需要知道,对于叶子节点,它的平衡因子都是0。
单旋转
首先我们来看单旋转,单旋转分为两种情况:左单旋和右单旋
示例代码:
//左旋
void _RotateL(Node *parent)
{
assert(parent);
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
parent->_right = SubRL;
if (SubRL)
SubRL->_parent = parent;
Node * ppNode = parent->_parent; //记录根的父亲
SubR->_left = parent;
parent->_parent = SubR;
if (ppNode==NULL) //考虑根节点的情况
{
_root = SubR;
SubR->_parent = NULL;
}
else
{
if (ppNode->_left == parent) //判断旋转以后的根应该链接在根的父亲的那边
{
ppNode->_left = SubR;
}
else
{
ppNode->_right = SubR;
}
SubR->_parent = ppNode;
}
parent->_bf = SubR->_bf = 0; //重置平衡因子
}
//右旋
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 == NULL) //考虑根节点情况
{
_root = SubL;
SubL->_parent = NULL;
}
else
{
if (ppNode->_left == parent) //判断旋转以后的根应该链接在根的父亲的那边
{
ppNode->_left = SubL;
}
else
{
ppNode->_right = SubL;
}
SubL->_parent = ppNode;
}
parent->_bf = SubL->_bf = 0; //重置平衡因子
}
双旋转
平衡二叉树有时的旋转是双旋转,双旋转有一个特点,就是他的parent节点和下一个节点平衡因子需要异号。
在这,根据sublr的平衡因子不同,分为了3种情况进行插入。接下来,我们对每一种情况进行分析,讲解。
第一种情况:
是sublr的平衡因子为0,这个时候就可以把sublr当作一个要插入的叶子节点来理解,这样subl的平衡因子为1,parentde平衡因子为-2,这样,就构成了双旋转。
第二种情况:
sublr的平衡因子为-1,这个时候就是在sublr的左子树进行插入节点操作,这样,subl的平衡因子变为1,parent的平衡因子变为-2。
第三种情况:
第三种情况所说的就是sublr的平衡因子为1,这个时候插入点是sublr的右树。这样,subl的平衡因子变为1,parent的平衡因子变为-2。
上述的就是进行右左双旋时的三种情况,我们可以画一个表格总结下:
示例代码:
//左右双旋
void _RotateLR(Node *parent)
{
//进行双旋转的时候通过在这里的旋转以后的根节点的bf进行判断
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
//计算旋转以后的根节点的bf
int bf = SubLR->_bf;
_RotateL(SubL);
_RotateR(parent);
//如果bf为0,代表插入点就是这个节点,这个时候所有旋转后的bf皆为0
if (bf == 0)
{
SubL->_bf = parent->_bf = 0;
}
//当bf为1时,这个时候相当于是在给bf的右树插入,所以插入以后subL旋转后的右边高度为h,
else if (bf == 1)
{
SubL->_bf = -1;
parent->_bf = 0;
}
//当bf为-1,这时相当于是给bf的左树进行插入
else
{
SubL->_bf = 0;
parent->_bf = 1;
}
SubLR->_bf = 0;
}
右左双旋转
接下来我们进行另外一种双旋转的分析,右左双旋,有了上面分析的基础,我相信,下面的分析,你一看就能理解,通过单旋转,我们就可以看到,旋转是镜像的
- subrl的bf==0
- subrl的bf==1
- subrl的bf==-1
由于右左旋转和上述的左右旋转分析的思路是一样的,所以我也不就在太详细的介绍了。
void _RotateRL(Node *parent)
{
//双旋转根据插入不同进行调整pf,
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
//根据最后的根节点的bf进行调整
int bf = SubRL->_bf;
_RotateR(SubR);
_RotateL(parent);
//当bf为0,代表插入的就是他这个节点,所有的bf都变作0
if (bf == 0)
{
parent->_bf = SubR->_bf = 0;
}
//当bf为-1,代表这时是在这个根的左树插入,h-1高度变作h。
else if (bf == -1)
{
parent->_bf = 0;
SubR->_bf = 1;
}
//当bf为1时,代表是在这个旋转后的根的右树插入,h-1的高度作h。
else
{
parent->_bf = -1;
SubR->_bf = 0;
}
SubRL->_bf = 0;
}
4.二叉平衡树的插入和删除
平衡二叉树的插入算法,和搜索二叉树的插入算法类似,依然是查找到为空的地方,然后进行创建新节点,进行插入,不过这里多了一个parent指针,所以要记得维护,又因为平衡二叉树的特性,所以不能忘了要从插入节点向上检查,确保向上始终为平衡二叉树,否则,需要进行平衡化旋转。
//插入算法
bool Insert(const K& key,const V& value)
{
if (_root == NULL)
{
_root = new Node(key, value);
return true;
}
Node* parent = NULL;
Node* cur = _root;
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->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
while (parent)
{
if (parent->_left == cur)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0)
{
return true;
}
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)
{
if (cur->_bf == 1)
{
_RotateL(parent);
return true;
}
else
{
_RotateRL(parent);
//parent->_bf = _Height(parent->_right) - _Height(parent->_left);
//_RotateRL(parent);
return true;
}
}
if (parent->_bf == -2)
{
if (cur->_bf == 1)
{
_RotateLR(parent);
//
旋转完以后必须进行重新对parent配置_bf
//parent->_bf = _Height(parent->_right) - _Height(parent->_left);
return true;
}
else
{
_RotateR(parent);
return true;
}
}
}
}
return true;
}
至于删除算法,因为删除的很复杂,在书上看到的是如果在你删除的节点数少与一半的时候,这个时候推荐使用懒惰删除,
规则就是:简单的标记要删除的节点是为已删除。然后在查找等例程里面都人为的忽视标记过的节点。如果标记删除的节点大于总数一半,就进行一次遍历,删除所有标记节点。
5.二叉平衡树的查找
AVL树的
bool Find(const K& key)
{
if (_root == NULL)
return false;
Node *cur = _root;
while (cur)
{
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key>cur->_key)
{
cur = cur->_right;
}
else
{
return true;
}
}
return false;
}
查找和搜索二叉树的类似,在这里我就不多说了,直接晒代码。因为在你插入中已经有这方面的逻辑了。
6.总结
AVL树是为了解决二叉搜素树中类似链式的树的时间复杂度太高的问题,如果采用了AVL树,大致时间复杂度都可以变为O(logN),这样就更加高效了。AVL树种最关键的就是它的旋转,分为单旋转和多旋转。AVL树是后续学习红黑树的基础。
#pragma once
#include<iostream>
#include<cstdlib>
#include<cassert>
using namespace std;
template<typename K,typename V>
struct AVLBinaryTreeNode
{
typedef AVLBinaryTreeNode<K, V> Node;
AVLBinaryTreeNode(const K& key,const V& value)
:_left(NULL)
,_right(NULL)
, _parent(NULL)
,_key(key)
, _bf(0)
, _value(value)
{}
Node* _left;
Node* _right;
Node* _parent;
int _bf; //来保存左右子树的高度差,平衡因子。
K _key;
V _value;
};
template<typename K, typename V>
class AVLBinaryTree
{
public:
typedef AVLBinaryTreeNode<K, V> Node;
public:
//构造函数
AVLBinaryTree()
:_root(NULL)
{
}
//析构函数
~AVLBinaryTree()
{
_DestoryTree(_root);
}
//拷贝构造
AVLBinaryTree(const AVLBinaryTree<K, V> & a)
{
_root = _Copy(_root);
}
//现代写法赋值
AVLBinaryTree<K,V>operator =(const AVLBinaryTree<K,V>& d)
{
if (this != &d)
{
AVLBinaryTree<K,V> tmp(d);
std::swap(tmp._root,_root);
}
return *this;
}
//插入节点
bool Insert(const K& key,const V& value)
{
if (_root == NULL)
{
_root = new Node(key, value);
return true;
}
Node* parent = NULL;
Node* cur = _root;
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->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
while (parent)
{
if (parent->_left == cur)
{
parent->_bf--;
}
else
{
parent->_bf++;
}
if (parent->_bf == 0)
{
return true;
}
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)
{
if (cur->_bf == 1)
{
_RotateL(parent);
return true;
}
else
{
_RotateRL(parent);
//parent->_bf = _Height(parent->_right) - _Height(parent->_left);
//_RotateRL(parent);
return true;
}
}
if (parent->_bf == -2)
{
if (cur->_bf == 1)
{
_RotateLR(parent);
//
旋转完以后必须进行重新对parent配置_bf
//parent->_bf = _Height(parent->_right) - _Height(parent->_left);
return true;
}
else
{
_RotateR(parent);
return true;
}
}
}
}
return true;
}
//查找节点
bool Find(const K& key)
{
if (_root == NULL)
return false;
Node *cur = _root;
while (cur)
{
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key>cur->_key)
{
cur = cur->_right;
}
else
{
return true;
}
}
return false;
}
//判断是否为AVL树
//bool IsAVLBinaryTree()
//{
// return _IsAVLBinaryTree(_root);
//}
bool IsAVLBinaryTree()
{
int depth = 0;
return _IsAVLBinaryTree(_root, depth);
}
//中序遍历
void Inorderprint()
{
_Inorderprint(_root);
cout << endl;
}
//得到高度
size_t Height() const
{
_Height(_root);
}
protected:
//拷贝构造二叉树
Node* _Copy(Node *root)
{
Node * NewNode = NULL;
if (root != NULL)
{
NewNode = new Node(root->_value);
NewNode->_left = _Copy(root->_left);
NewNode->_right = _Copy(root->_right);
}
return NewNode;
}
//树的高度
size_t _Height(Node *root)
{
if (root == NULL)
return 0;
size_t lheight = _Height(root->_left) + 1;
size_t rheight = _Height(root->_right) + 1;
return lheight > rheight ? lheight : rheight;
}
//判断二叉树是否为平衡二叉树
/*bool _IsAVLBinaryTree(Node *root)
{
if (root==NULL)
{
return true;
}
int heightorder = abs((int)(_Height(root->_right) - _Height(root->_left)));
return ((heightorder<2)
&&(_IsAVLBinaryTree(root->_left))
&& (_IsAVLBinaryTree(root->_right)));
}*/
//判断二叉树是否为平衡二叉树,优化版本
bool _IsAVLBinaryTree(Node *root, int & depth)
{
//如果为空,往父节点返
if (root == NULL)
{
depth = 0;
return true;
}
//记录左节点和右节点深度
int left = 0;
int right = 0;
//采取传引用的方式,下层递归进行对深度修改以后会影响上一层
if (_IsAVLBinaryTree(root->_left, left) && _IsAVLBinaryTree(root->_right, right))
{
//计算平衡因子
int pf = right - left;
//判断平衡因子是否合法
if (pfIsInvaild(pf))
{
//合法就让自身加上子树的深度,然后因为是传引用,所以当递归回到上一层的时候,上层中的right和left就是左右子树的深度
depth = 1 + (right > left ? right : left);
return true;
}
}
return false;
}
//判断平衡因子是否合法
bool pfIsInvaild(const int& pf)
{
return abs(pf) < 2;
}
//中序递归遍历
void _Inorderprint(Node *root)
{
if (root == NULL)
{
return;
}
_Inorderprint(root->_left);
cout << root->_key<<" ";
cout << root->_bf << endl;
_Inorderprint(root->_right);
}
//销毁AVL树
Node* _DestoryTree(Node*& root)
{
if (root != NULL)
{
root->_left = _DestoryTree(root->_left);
root->_right = _DestoryTree(root->_right);
delete root;
root = NULL;
}
return root;
}
//左旋
void _RotateL(Node *parent)
{
assert(parent);
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
parent->_right = SubRL;
if (SubRL)
SubRL->_parent = parent;
Node * ppNode = parent->_parent; //记录根的父亲
SubR->_left = parent;
parent->_parent = SubR;
if (ppNode==NULL) //考虑根节点的情况
{
_root = SubR;
SubR->_parent = NULL;
}
else
{
if (ppNode->_left == parent) //判断旋转以后的根应该链接在根的父亲的那边
{
ppNode->_left = SubR;
}
else
{
ppNode->_right = SubR;
}
SubR->_parent = ppNode;
}
parent->_bf = SubR->_bf = 0; //重置平衡因子
}
//先左旋后右旋
//void _RotateLR(Node *parent)
//{
// Node* SubL = parent->_left;
// Node* SubLR = SubL->_right;
// SubL->_right = SubLR->_left;
// if (SubLR->_left)
// SubL->_right->_parent = SubL;
// SubLR->_left = SubL;
// SubL->_parent = SubLR;
// parent->_left = SubLR;
// SubLR->_parent = parent;
// //判断SubLR的左子树是否存在,如果存在,在调整过程当中,需要进行bf的调整
// //SubLR的left最后是变成了SubL的left,所以当pf为1时,这个时候调整过去,SubL的pf就是左树比右树大。
// if (SubLR->_bf <= 0)
// SubL->_bf = 0;
// else
// SubL->_bf = -1;
// parent->_left = SubLR->_right;
// if (SubLR->_right)
// {
// parent->_left->_parent = parent;
// }
// Node *ppnode = parent->_parent;
// SubLR->_right = parent;
// parent->_parent = SubLR;
// if (ppnode == NULL)
// {
// _root = SubLR;
// SubLR->_parent = NULL;
// }
// else
// {
// if (parent == ppnode->_left)
// {
// ppnode->_left = SubLR;
// }
// else
// {
// ppnode->_right = SubLR;
// }
// SubLR->_parent = ppnode;
// }
// //同上,这是看SubLR的right是否存在。
// if (SubLR->_bf == -1)
// parent->_bf = 1;
// else
// parent->_bf = 0;
// SubLR->_bf = 0;
//}
void _RotateLR(Node *parent)
{
//进行双旋转的时候通过在这里的旋转以后的根节点的bf进行判断
Node* SubL = parent->_left;
Node* SubLR = SubL->_right;
//计算旋转以后的根节点的bf
int bf = SubLR->_bf;
_RotateL(SubL);
_RotateR(parent);
//如果bf为0,代表插入点就是这个节点,这个时候所有旋转后的bf皆为0
if (bf == 0)
{
SubL->_bf = parent->_bf = 0;
}
//当bf为1时,这个时候相当于是在给bf的右树插入,所以插入以后subL旋转后的右边高度为h,
else if (bf == 1)
{
SubL->_bf = -1;
parent->_bf = 0;
}
//当bf为-1,这时相当于是给bf的左树进行插入
else
{
SubL->_bf = 0;
parent->_bf = 1;
}
SubLR->_bf = 0;
}
//先右旋后左旋
//void _RotateRL(Node *parent)
//{
// Node *SubR = parent->_right;
// Node *SubRL = SubR->_left;
// //右旋
// SubR->_left = SubRL->_right;
// if (SubRL->_right)
// SubR->_right->_parent = SubR;
// parent->_right = SubRL;
// SubRL->_parent = parent;
// SubRL->_right = SubR;
// SubR->_parent = SubRL;
// if (SubRL->_bf >= 0)
// SubR->_bf = 0;
// else
// SubR->_bf = 1;
// //左旋
// parent->_right = SubRL->_left;
// if (SubRL->_left)
// parent->_right->_parent = parent;
// Node *ppNode = parent->_parent;
// SubRL->_left = parent;
// parent->_parent = SubRL;
// if (ppNode == NULL)
// {
// _root = SubRL;
// SubRL->_parent = NULL;
// }
// else
// {
// if (ppNode->_left == parent)
// {
// ppNode->_left = SubRL;
// }
// else
// {
// ppNode->_right = SubRL;
// }
// SubRL->_parent = ppNode;
// }
// if (SubRL->_bf == 1)
// parent->_bf = -1;
// else
// parent->_bf = 0;
//}
//右旋
void _RotateRL(Node *parent)
{
//双旋转根据插入不同进行调整pf,
Node* SubR = parent->_right;
Node* SubRL = SubR->_left;
//根据最后的根节点的bf进行调整
int bf = SubRL->_bf;
_RotateR(SubR);
_RotateL(parent);
//当bf为0,代表插入的就是他这个节点,所有的bf都变作0
if (bf == 0)
{
parent->_bf = SubR->_bf = 0;
}
//当bf为-1,代表这时是在这个根的左树插入,h-1高度变作h。
else if (bf == -1)
{
parent->_bf = 0;
SubR->_bf = 1;
}
//当bf为1时,代表是在这个旋转后的根的右树插入,h-1的高度作h。
else
{
parent->_bf = -1;
SubR->_bf = 0;
}
SubRL->_bf = 0;
}
//右旋
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 == NULL) //考虑根节点情况
{
_root = SubL;
SubL->_parent = NULL;
}
else
{
if (ppNode->_left == parent) //判断旋转以后的根应该链接在根的父亲的那边
{
ppNode->_left = SubL;
}
else
{
ppNode->_right = SubL;
}
SubL->_parent = ppNode;
}
parent->_bf = SubL->_bf = 0; //重置平衡因子
}
protected:
Node* _root;
};