二叉搜索树给我们了一个搜索一个点的方法,同时,也将二叉树中的节点排序了,但是,对于一些特殊的二叉树来说,使用二叉搜索树会很费空间,比如说:
左单支的这种情况,你说是二叉树,还不如说是单链表呢,还节省了一大波的空间呢(右指针域),同样的,对于右单支的情况也是如此,那么现在我们就要想能不能避免这个问题。
可以,一个平衡因子就可以搞定,加了平衡因子,那么这颗二叉搜索树就是AVLTree了
那么现在我们就来分析一下AVLTree,首先还是看一下平衡因子吧:平衡因子的实质就是右子树的高度减去左子树的高度的差值(当然你也可以用左减去右),既然是平衡因子,那么就有一个范围,这里的范围就是:-1,0,1。每一个结点的平衡因子都是这三个值中的一个,不是,那么就不平衡。那么为什么会是这三个呢?
下面我们来分析一下,首先0就不用说了,表示树的左右子树高度相等的,当然是平衡的,那么1和-1呢,这其实都是一个类型的,我们之说一个就行了,就按照-1来说吧,我们举一个最简单的例子。如果一个树只有两个节点,那么会出现什么结果呢?
这就是-1的结果,因此,所有,结点的右子树的高度减去左子树的高度的差值的绝对值如果大于1,那么他就不是平衡树。
了解了这些之后,我们就来构建一颗AVLTree吧
首先还是构建一个树的结点,其实二叉搜索树都已经写过了,这个都很简单了,这就比二叉搜索树多了一个平衡因子而已嘛。
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode(const K& key, const V& value)
: _pLeft(NULL)
, _pRight(NULL)
, _pParent(NULL)
, _key(key)
, _value(value)
, _bf(0)
{}
AVLTreeNode *_pLeft; //左子树
AVLTreeNode *_pRight; // 右子树
AVLTreeNode *_pParent; //双亲
K _key;
V _value;
int _bf; // 平衡因子:right-left
};
然后我们按照一定的规则来插入元素,其实这些规则都是二叉搜索树的,无非就是当你的Key小于当前节点的Key的时候,从左边遍历,大于就从右边遍历,找到之后就插入,不过这里在插入之后要判断是否满足平衡树的条件,如果不满足的话,那么需要调平。
那么我们就重点来看一下怎样调平的吧:
首先插入进去之后平衡因子会变化,那么都有哪些结点的平衡因子变化了呢,我们不妨仔细看看,
这张图中红色的线链接的那个结点是新插入的结点,在插入之前结点:30、40、50的平衡因子都是0,但是插入之后,他们就变成了-1,他们还有一个共同点,那就是,他们都是新插入节点的祖先结点,也就是说,新插入一个结点,那么从根结点到此节点这条链的所有节点的平衡因子都变化。
至于说,如何变化的,显然如果是其左子树,那么-1,右子树+1
最后就是调平了,我们二叉树中的一条链上的结点的平衡因子变化了,那么就得看一下变化之后是不是还满足AVLTree的条件。
总体来说还是得分情况:
1、如果此节点的平衡因子变成0,那么直接就不用调了。
2、如果此节点是1或-1的话,还是平衡树,那么继续向上边遍历,看是否有超过1的
3、如果此节点的平衡因子的绝对值超过1,显然不满足平衡树的条件,这就需要我们做一下旋转处理了。
旋转大致分为四种:左单旋——右右,右单旋——左左,左右双旋——左右,右左双旋——右左。
那么我们来一个一个看,首先来看一下左单旋:
右右的意思是:在此节点右子树的右侧插入一个节点。
如图所示,其中65或80这两个结点都满足这个旋转条件,为了方便起见,我们只取一个,来看一下它的旋转处理:
我们将55这个结点的连接到50的右,同时将50连接到60的左。不过,在做这些之前,我们需要将一些节点先保存起来,这样,变化之后,我们再将50和60这两个结点的平衡因子都置为0。
//左单旋——右右
void _RotateL(Node* parent)
{
Node* subR = parent->_pRight;
Node* subRL = subR->_pLeft;//有可能不存在
parent->_pRight = subRL;
if (subRL)
subRL->_pParent = parent;
subR->_pLeft = parent;
Node* gparent = parent->_pParent;//保存parent的双亲
parent->_pParent = subR;
subR->_pParent = gparent;
if (gparent == NULL)//parent是根结点
_pRoot = subR;
else if (gparent->_pLeft == parent)
gparent->_pLeft = subR;
else
gparent->_pRight = subR;
//旋转之后将其清零——结合图理解
parent->_bf = 0;
subR->_bf = 0;
}
右单旋——左左
左左的意思是:在此节点的左子树的左侧,其实如果你理解了左单旋,你会发现,右单旋就和左单旋互换一个左右即可。
这里的20和35都是新插入的结点,同样的 ,我们也取出一个作为例子来看它的旋转
这个理念和做单选的一样,在这里做出一个建议,不要去尝试记忆两个,而要试着理解一个,那么另一个就像镜子里的自己,显而易见的。
双旋也就是两个单旋的组合,而左右双旋和右左双旋的区别就是左单旋和右单旋的顺序问题。
左右单旋:先进行左单旋,再进行右单旋
这就是一个可以进行左右单旋的例子,那么来看一下它的旋转吧
同样的右左双旋的理念也是这样的:先进行右旋,在进行左旋。
那么接下来我们就来看一下实现的过程吧,这个过程我debug的好多次,其中有各种的小毛病,注释上面都有,所以,看到的都要引以为戒。
AVLTree创建成功之后,我们得测验一下吧,就是判断这个二叉树是不是平衡树,按照二叉树的定义来,我们只需判断根结点的平衡因子是不是满足条件即可,然后再递归左子树和右子树。
不过,这里有一个需要注意的地方,就是平衡因子的计算是不是正确的,这个就是我刚才说的小毛病,所以,在判断平衡因子之前,我们再来计算一下平衡因子,如果和结点中的平衡因子不同,那么就不用在看其是否满足条件了。
// 检测二叉树是否为平衡树?
bool _IsBalanceTree(Node* pRoot)
{
if (pRoot == NULL)
return true;
//求出左右子树的高度
size_t left = _Height(pRoot->_pLeft);
size_t right = _Height(pRoot->_pRight);
//如果平衡因子计算错误 或 平衡因子大于1 那么不是平衡树
if ((right - left) != pRoot->_bf || pRoot->_bf > 1)
return false;
return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot->_pRight);
}
下面是全部的代码,这里的测试用例很好,基本上涵盖了所有的旋转问题。
#include<iostream>
using namespace std;
template<class K, class V>
struct AVLTreeNode
{
AVLTreeNode(const K& key, const V& value)
: _pLeft(NULL)
, _pRight(NULL)
, _pParent(NULL)
, _key(key)
, _value(value)
, _bf(0)
{}
AVLTreeNode<K, V> *_pLeft; //左子树
AVLTreeNode<K, V> *_pRight; // 右子树
AVLTreeNode<K, V> *_pParent; //双亲
K _key;
V _value;
int _bf; // 平衡因子:right-left
};
template<class K, class V>
class AVLTree
{
typedef AVLTreeNode<K, V> Node;
public:
AVLTree()
: _pRoot(NULL)
{}
bool Insert(const K& key, const V& value)
{
if (_pRoot == NULL)//如果树为空,直接插入
{
_pRoot = new Node(key, value);
return true;
}
//找插入位置
Node* pCur = _pRoot;
Node* parent = NULL;
while (pCur)
{
if (key < pCur->_key)
{
parent = pCur;
pCur = pCur->_pLeft;
}
else if (key>pCur->_key)
{
parent = pCur;
pCur = pCur->_pRight;
}
else
return false;
}
//插入
pCur = new Node(key, value);//不要忘
if (key < parent->_key)
parent->_pLeft = pCur;
else
parent->_pRight = pCur;
pCur->_pParent = parent;//插入之后将pCur的双亲的指针指向parent
while (parent != NULL)
{
//平衡树可能不平衡了,重新调整
if (parent->_pLeft == pCur)
parent->_bf--;//不管pCur的兄弟结点是否存在,直接--,即可
else
parent->_bf++;
//分情况讨论
if (parent->_bf == 0)//层数没有增加
return true;
else if (parent->_bf == 1 || parent->_bf == -1)//层数加一,此时这个子树还是平衡的,继续向上调整
{
pCur = parent;
parent = parent->_pParent;
}
else// 2||-2 已经不平衡了,需要进行旋转处理
{
if (parent->_bf == 2)
{
if (pCur->_bf == 1)
_RotateL(parent);//右右——左单旋
else
_RotateRL(parent);//右左——右左双旋
}
else
{
if (pCur->_bf == -1)
_RotateR(parent);//左左——右单旋
else
_RotateLR(parent);//左右——左右双旋
}
//插入只是影响树的局部,调整之后,树平衡
break;
}
}
}
void InOrder()
{
cout << "InOrder: ";
_InOrder(_pRoot);
cout << endl;
}
size_t Height()
{
return _Height(_pRoot);
}
bool IsBalanceTree()
{
return _IsBalanceTree(_pRoot);
}
private:
// 检测二叉树是否为平衡树?
bool _IsBalanceTree(Node* pRoot)
{
if (pRoot == NULL)
return true;
//求出左右子树的高度
size_t left = _Height(pRoot->_pLeft);
size_t right = _Height(pRoot->_pRight);
//如果平衡因子计算错误 或 平衡因子大于1 那么不是平衡树
if ((right - left) != pRoot->_bf || pRoot->_bf > 1)
return false;
return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot->_pRight);
}
size_t _Height(Node* pRoot)
{
if (pRoot == NULL)
return 0;
if (pRoot->_pLeft == NULL&&pRoot->_pRight == NULL)
return 1;
size_t left = _Height(pRoot->_pLeft);
size_t right = _Height(pRoot->_pRight);
return left > right ? left + 1 : right + 1;
}
void _InOrder(Node* pRoot)
{
if (pRoot)
{
_InOrder(pRoot->_pLeft);
cout << pRoot->_key << " ";
_InOrder(pRoot->_pRight);
}
}
//左单旋——右右
void _RotateL(Node* parent)
{
Node* subR = parent->_pRight;
Node* subRL = subR->_pLeft;//有可能不存在
parent->_pRight = subRL;
if (subRL)
subRL->_pParent = parent;
subR->_pLeft = parent;
Node* gparent = parent->_pParent;//保存parent的双亲
parent->_pParent = subR;
subR->_pParent = gparent;
if (gparent == NULL)//parent是根结点
_pRoot = subR;
else if (gparent->_pLeft == parent)
gparent->_pLeft = subR;
else
gparent->_pRight = subR;
//旋转之后将其清零——结合图理解
parent->_bf = 0;
subR->_bf = 0;
}
//右单旋——左左(在较高左子树的左插入结点)
void _RotateR(Node* parent)
{
Node* subL = parent->_pLeft;
Node* subLR = subL->_pRight;
parent->_pLeft = subLR;
if (subLR)
subLR->_pParent = parent;
subL->_pRight = parent;
Node* gparent = parent->_pParent;
parent->_pParent = subL;
subL->_pParent = gparent;//总共三个,一个都不能忘
if (gparent == NULL)
_pRoot = subL;
else if (gparent->_pLeft == parent)
gparent->_pLeft = subL;
else
gparent->_pRight = subL;
parent->_bf = 0;
subL->_bf = 0;
}
//左右双旋——左右
void _RotateLR(Node* parent)
{
Node* subL = parent->_pLeft;
Node* subLR = subL->_pRight;
int bf = subLR->_bf;
_RotateL(subL);
_RotateR(parent);
if (-1 == bf)
parent->_bf = 1;
else if (1 == bf)
subL->_bf = -1;
else//只有三个结点的时候
return;
}
//右左双旋——右左
void _RotateRL(Node* parent)//debug
{
Node* subR = parent->_pRight;
Node* subRL = subR->_pLeft;
int bf = subRL->_bf;
_RotateR(subR);
_RotateL(parent);
if (1 == bf)
parent->_bf = -1;
else if (-1 == bf)
subR->_bf = 1;
else
return;
}
private:
Node* _pRoot;
};
void FunTest()
{
int array[] = {16, 3, 7, 11, 9, 26, 18, 14, 15};
AVLTree t;
for (size_t idx = 0; idx < sizeof(array) / sizeof(array [0]); ++idx)
t.Insert(array[idx], idx);
t.InOrder();
if (t.IsBalanceTree())
{
cout << "是AVL树" << endl;
}
else
{
cout << "不是AVL树" << endl;
}
}
int main()
{
FunTest();
return 0;
}