AVL树的概念:
我们都知道搜索二叉树是 小的数据在当前根节点的左边 大的数据在根节点的右边 。虽说这种结构在查找数据的时候会很方便,但是如果现在我们的数据是接近有序的那么,这个二叉树就可能退化成为单支树 这样我们的查找效率并没有得到改善 此时我们称这种情况为不平衡,所以就有了现在的平衡二叉树保障了搜索的效率
现在我们就可以通过保证 左右子树高度差(右减去左)不超过-1和1 和 左右子树都为AVL树来 实现我们整体的AVL树
选择实现的方法不只一种 可选择用平衡因子的方式,这个到底最终也只是以个实现的选择
实现AVL树:
要实现一个AVL树,那么就需要控制好在对它操作时,节点之间的平衡关系,那这个就需要根据不同的情况进行对应的旋转操作!!!
插入:
我们首先插入的规则肯定还是按照搜索二叉树的规则来的,可是插完数据后又该怎样更新平衡因子呢,又该怎样进行旋转呢?这些都是在插入操作时需要考虑的问题。
对于更新平衡因子,这个在我们组织节点的时候就应该考虑到,因为当我们插入一个节点后平衡因子影响的是自己的祖先,所以我们采用的是三叉链的结构(也就是多一个指针指向父亲节点)这样也方便我们回溯去更新平衡因子
① 如何更新一个节点平衡因子就只需要看新插入节点在这个节点的左子树还是右子树就可以啦
在右子树就+1 在左子树就-1 如果在更新过程中发现更新后的平衡因子违反规则那么就要对其进行旋转调整 一定要注意自己 子树高度 是否有变化 没有变化就没必要再往上更新了
旋转:
旋转的原则:遵守规则,恢复平衡
右边高--左单旋
大家看这个图片里的样例违规了嘛?
违规了!!! 这里是很明显的根节点的右边子树高了:应该左单旋
void RotateL(node* parent) { node* cur = parent->_right; node* curL = cur->_left; parent->_right = curL; if (curL) //left可能为空 curL->_parent = parent; node* tmpNode = parent->_parent; //已保存,可更改parent的父节点 parent->_parent = cur; cur->_left = parent; if (parent == _root) { cur = _root; cur->_parent = nullptr; } else { if (tmpNode->_left == parent) { tmpNode->_left = cur; } else { tmpNode->_right = cur; } cur->_parent = tmpNode; } cur->_vb = 0; parent->_vb =0;//更新平衡因子 }
在写这部分的代码的时候,要小心一些,有些节点是有可能是为空的,又因为这个是一个三叉链,也要注意注意父亲节点的更新,以及更新前状态的保存
对于左边高--右单旋也是一样的:
代码的实现原理和左单选是一样的!!!!(这个大家可以自己尝试一下)
双旋:
这个实现起来并不复杂,但是要注意双选后平衡因子的更新!!
大家看这幅图,觉得应该怎样的旋转呢?
面对这种情况,其实就先进行左单旋,这样我们就能明显发现现在的情况已经是一个明显的左边高需要右单旋的情况。 当然与之相同的也有右左双旋
大家看下面这三张图就知道为什么平衡因子会需要注意了
明明都是双旋为什么平衡因子不一样呢:我们进行了两次单旋,那么就肯定有两个子树移接到其他子树上了,也就代表平衡因子也会跟着发生对应的变化 所以我们在更新平衡因子的时候也得注意
void RotateLR(node* parent) { node* cur = parent->_left; node* curR = cur->_right; int bf = curR->_vb; LotateR(cur); RotateL(parent); if (bf == 0) { parent->_vb = 0; cur->_vb = 0; curR->_vb = 0; } else if (bf == 1) { cur->_vb = -1; curR->_vb = 0; parent->_vb = 0; } else if (bf == -1) { cur->_vb = 0; curR->_vb = 0; parent->_vb = 1; } else { assert(false); } }
除了上面的左右双旋 还有 右左双旋 实现的思路都是一样的,所以大家可以自己下来推导一下!!