二叉搜索树给我们了一个搜索一个点的方法,同时,也将二叉树中的节点排序了,但是,对于一些特殊的二叉树来说,使用二叉搜索树会很费空间,比如说:
左单支的这种情况,你说是二叉树,还不如说是单链表呢,还节省了一大波的空间呢(右指针域),同样的,对于右单支的情况也是如此,那么现在我们就要想能不能避免这个问题。
可以,一个平衡因子就可以搞定,加了平衡因子,那么这颗二叉搜索树就是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<K, V> *_pLeft; //左子树
AVLTreeNode<K, V> *_pRight; // 右子树
AVLTreeNode<K, V> *_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