赞
踩
摘要:本文介绍二叉搜索树的改进AVL树(平衡二叉树),将对其概念与实现两部分展开。主要介绍其中的插入过程,其难点主要在于4个旋转过程。这个四个旋转过程,作者从几何特征,平衡因子特征出发,观察其变化情况与细节以及平衡因子的改变。最后进行性能分析的简要介绍。由于删除考察得比较少,因此本文并不对此进行介绍。
二叉搜索树存在缺点,二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
平衡二叉树(AVL树)可以定义为或者是空树,或者是具有以下性质的二叉搜索树:
- 它的左右子树都是AVL树
- 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
使用代码实现时,其定义如下:对于树的节点使用三叉链的方式,其中还包括对应的键值对,在此设计为包括平衡因子,便于后续的实现,代码示例如下:
template<class K,class V> class AVLTreeNode{ public: AVLTreeNode<K, V>* _left; AVLTreeNode<K, V>* _right; AVLTreeNode<K, V>* _parent; pair<K, V> _keyValue; // 键值对 int _balanceFactor; // 平衡因子 AVLTreeNode(const pair<K, V>& keyValue) :_left(nullptr) ,_right(nullptr) ,_parent(nullptr) ,_keyValue(keyValue) ,_balanceFactor(0) {} }; template<class K,class V> class AVLTree { typedef AVLTreeNode<K, V> AVLTreeNode; private: AVLTreeNode* _root; public: AVLTree() :_root(nullptr); {} };
AVL树的插入是在搜索二叉树的条件下进行改进,从而达到平衡的目的。因此主要基本思想可以分为:
该步的方法与搜索二叉树的插入过程相似,首先找出键值对的键找出插入位置,其中记录parent
和cur
指针,便于在找到后简单的找出与父节点连接的方向以及完成插入节点的parent
指针的连接。
if (_root == nullptr) { _root = new AVLTreeNode(kv); return true; } // 根据二叉搜索树的规则寻找插入位置 AVLTreeNode* parent = nullptr; AVLTreeNode* cur = _root; while (cur) { if (cur->_keyValue.first > kv.first) { parent = cur; cur = cur->_left; } else if (cur->_keyValue.first < kv.first) { parent = cur; cur = cur->_right; } else { return false; } } // 插入新节点 cur = new AVLTreeNode(kv); if (parent->_keyValue.first > cur->_keyValue.first) { parent->_left = cur; cur->_parent = parent; } else { parent->_right = cur; cur->_parent = parent; }
在插入完新节点后,需要对平衡因子进行更新,平衡因子的概念为:左右之树之差,当左子树高时为-1,右子树高时为1,相等为0,其他情况皆为异常状况。
插入新节点后,会影响到的节点,就是新插入节点的祖先们,因为新插入节点改变了这些祖先们子树的高度关系,因此可以通过parent
指针来完成更新平衡因子的工作,规则如下:
根据cur
插入的位置,对于parent
的平衡因子进行更新,当插入在parent
的左子树时,balanceFactor
需要减少,插入在右子树时需要增加balanceFactor
更新以后,如果parent
的平衡因子为0,更新结束。因为平衡因子更新前,parent树只有左子树或者只有右子树,插入新节点后,将空的一边填补,parent
的高度不变,对于其祖先没有影响,图示如下:
更新以后,如果parent
的平衡因子为±1,说明插入前parent
的平衡因子一定为0,插入后被更新成±1,此 时以parent
为根的树的高度增加,需要继续向上更新
更新以后,如果parent
的平衡因子为±2,则parent
的平衡因子违反平衡树的性质,需要对其进行旋转处理
过程代码如下:通过while
循环以及parent
的更新来完成对祖先的访问
// 更新平衡因子 while (parent){ //根据cur插入的位置,对于parent的平衡因子进行更新, //当插入在parent的左子树时,balanceFactor需要减少, //插入在右子树时需要增加balanceFactor if (parent->_left == cur) { parent->_balanceFactor--; } else { parent->_balanceFactor++; } if (parent->_balanceFactor == 0) { break } // 继续往上更新 else if (parent->_balanceFactor == 1 || parent->_balanceFactor == -1) { cur = parent; parent = parent->_parent; } else if (parent->_balanceFactor == 2 || parent->_balanceFactor == -2) // 对于AVL树平衡因子异常处理 } else{ // 说明插入更新平衡因子之前,树中平衡因子就有问题了 assert(false); } }
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
- 新节点插入较高左子树的左侧:右单旋
- 新节点插入较高右子树的右侧:左单旋
- 新节点插入较高左子树的右侧:先左单旋再右单旋
- 新节点插入较高右子树的左侧:先右单旋再左单旋
而这四种的旋转方式,需要符合两个规则分别为:保持搜索树的特性,并且控制平衡,以下对于这四种情况进行分别讲解:
几何特征:当新节点插入较高左子树的左侧时,将其需要将其像右旋转,图示如下:
平衡因子特征:parent->_balanceFactor == -2 && cur->_balanceFactor == -1
修改平衡因子:cur->_balanceFactor = parent->_balanceFactor = 0;
旋转意义:将高度较高的子树的最大节点上提为根,将原来的根下移为子树的根,使得高度平衡。如此原来高度为H+2的子树降低为H+1,原来H的子树提高为H+1。
实现过程:
从总体上观察,是将cur
的右子树作为了parent
的左子树,再将cur
的右子树指向parent
void RotateR(AVLTreeNode* parent) {
AVLTreeNode* cur = parent->_left;
AVLTreeNode* curR = cur->_right;
parent->_left = curR;
curR->_parent = parent;
cur->_right = parent;
parent->_parent = cur;
}
对于整棵树的整体已经变化完毕了,但是还需要对树外的指针进行操作,即对于parent
的parent
也要进行连接。如果parent
是根节点就不需要连接,将cur
作为根,否则就需要找出链接的子树,并完成相互链接。
void RotateR(AVLTreeNode* parent) { // cur一定存在 无需判断空 AVLTreeNode* cur = parent->_left; AVLTreeNode* curR = cur->_right; // 对于parent的parent也要进行连接 AVLTreeNode* parentParent = parent->_parent; parent->_left = curR; curR->_parent = parent; cur->_right = parent; parent->_parent = cur; // 如果parent是根节点就不需要连接,将cur作为根 if (parent == _root) { _root = cur; _root->_parent == nullptr; } // 将cur作为根,否则就需要找出链接的子树,并完成相互链接 else{ if (parentParent->_left == parent) { parentParent->_left = cur; cur->_parent = parentParent; } else { parentParent->_right = cur; cur->_parent = parentParent; } } }
由于存在空指针的情况,因此需要对节点的子树进行判断
void RotateR(AVLTreeNode* parent) { AVLTreeNode* cur = parent->_left; AVLTreeNode* curR = cur->_right; // 对于parent的parent也要进行连接 AVLTreeNode* parentParent = parent->_parent; parent->_left = curR; // curR可能为空,因此需要对其进行空指针的判断 if (curR != nullptr) { curR->_parent = parent; } cur->_right = parent; parent->_parent = cur; // 如果parent是根节点就不需要连接,将cur作为根 if (parent == _root) { _root = cur; _root->_parent == nullptr; } // 将cur作为根,否则就需要找出链接的子树,并完成相互链接 else{ if (parentParent->_left == parent) { parentParent->_left = cur; cur->_parent = parentParent; } else { parentParent->_right = cur; cur->_parent = parentParent; } } }
最后对平衡因子进行相应的修改
cur->_balanceFactor = parent->_balanceFactor = 0;
完整代码
void RotateR(AVLTreeNode* parent) { AVLTreeNode* cur = parent->_left; AVLTreeNode* curR = cur->_right; // 对于parent的parent也要进行连接 AVLTreeNode* parentParent = parent->_parent; parent->_left = curR; // curR可能为空,因此需要对其进行空指针的判断 if (curR != nullptr) { curR->_parent = parent; } cur->_right = parent; parent->_parent = cur; // 如果parent是根节点就不需要连接,将cur作为根 if (parent == _root) { _root = cur; _root->_parent == nullptr; } // 将cur作为根,否则就需要找出链接的子树,并完成相互链接 else{ if (parentParent->_left == parent) { parentParent->_left = cur; cur->_parent = parentParent; } else { parentParent->_right = cur; cur->_parent = parentParent; } } cur->_balanceFactor = parent->_balanceFactor = 0; }
几何特征:当新节点插入较高右子树的右侧时,将其需要将其像左旋转,图示如下:
平衡因子特征:parent->_balanceFactor == 2 && cur->_balanceFactor == 1
修改平衡因子:cur->_balanceFactor = parent->_balanceFactor = 0;
旋转意义:将高度较高的子树的最大节点上提为根,将原来的根下移为子树的根,使得高度平衡。如此原来高度为H+2的子树降低为H+1,原来H的子树提高为H+1。
实现过程:与右单旋类似,只是方向不同,在此不做过多赘述
void RotateL(AVLTreeNode* parent) { AVLTreeNode* cur = parent->_right; AVLTreeNode* curL = cur->_left; AVLTreeNode* parentParent = parent->_parent; parent->_right = curL; if (curL != nullptr) { curR->_parent = parent; } parent->_parent = cur; cur->_left = parent; if (parent == _root) { _root = cur; _root->_parent = nullptr; } else { if (parentParent->_left == parent) { parentParent->_left = cur; cur->_parent = parentParent; } else { parentParent->_right = cur; cur->_parent = parentParent; } } cur->_balanceFactor = parent->_balanceFactor = 0; }
几何特征:新节点插入较高左子树的右侧,先左单旋再右单旋,图示如下:
平衡因子特征:parent->_balanceFactor == -2 && cur->_balanceFactor == 1
修改平衡因子:可以通过curR
的balanceFactor
来判断
bf == 0 ,H = 0时,表示插入节点为curR时,此时的平衡因子为皆为0
当H ≠ 0时
当bf == -1
时,cur->_balanceFactor = curR->_balanceFactor = 0
以及parent->_balanceFactor = 1
当bf == 1
时,parent->_balanceFactor = curR->_balanceFactor = 0
以及cur->_balanceFactor = -1
旋转意义:如果单纯的进行右旋转是无意义的,因为给予给原来根节点的左子树,与cur
的左子树相比,旋转后仍然不符合AVL树的规则。为此,先通过左旋转来降低子树的高度,再进行右旋转进行第二次降低高度
实现过程:
完整代码:
void RotateLR(AVLTreeNode* parent) { AVLTreeNode* cur = parent->_left; AVLTreeNode* curR = cur->_right; int bf = curR->_balanceFactor; RotateL(cur); RotateR(parent); if (bf == 1) { parent->_balanceFactor = curR->_balanceFactor = 0; cur -> _balanceFactor = -1; } else if (bf == -1) { cur -> _balanceFactor = curR->_balanceFactor = 0; parent -> _balanceFactor = 1; } else if (bf == 0){ cur -> _balanceFactor = curR->_balanceFactor = parent->_balanceFactor = 0; } else { assert(false); } }
几何特征:新节点插入较高右子树的左侧,先右单旋再左单旋
平衡因子特征:parent->_balanceFactor == 2 && cur->_balanceFactor == -1
修改平衡因子:与左右双旋的分析方法一致,此处不做赘述
bf == -1
时,parent->_balanceFactor = curR->_balanceFactor = 0
以及cur->_balanceFactor = 1
bf == 1
时,cur->_balanceFactor = curR->_balanceFactor = 0
以及parent->_balanceFactor = -1
旋转意义:如果单纯的进行左旋转是无意义的,因为给予给原来根节点的右子树,与cur
的右子树相比,旋转后仍然不符合AVL树的规则。为此,先通过右旋转来降低子树的高度,再进行左旋转进行第二次降低高度
实现过程:与左右双旋一致,不再赘述
完整代码:
void RotateRL(AVLTreeNode* parent) { AVLTreeNode* cur = parent->_right; AVLTreeNode* curR = cur->_left; int bf = curR->_balanceFactor; RotateR(cur); RotateL(parent); if (bf == -1) { parent->_balanceFactor = curR->_balanceFactor = 0; cur -> _balanceFactor = 1; } else if (bf == 1) { cur -> _balanceFactor = curR->_balanceFactor = 0; parent -> _balanceFactor = -1; } else if (bf == 0) { cur->_balanceFactor = curR->_balanceFactor = parent->_balanceFactor = 0; } else { assert(false); } }
AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
中序遍历可得到一个有序的序列,就说明为二叉搜索树
public:
void InOrder() {
_InOrder(_root);
}
private:
void _InOrder(AVLTreeNode* root) {
if (root == NULL)
return;
_InOrder(root->_left);
cout << root->_keyValue.first << ":" << root->_keyValue.second << endl;
_InOrder(root->_right);
}
每个节点子树高度差的绝对值不超过1,节点的平衡因子是否计算正确。通过递归法求出树的高度,当平衡因子不符合树的高度差或者平衡因子异常时返回false,代码如下:
private: bool _IsBalance(AVLTreeNode* root) { if (root == NULL) return true; // 对当前树进行检查 int leftHeight = Height(root->_left); int rightHeight = Height(root->_right); if (rightHeight - leftHeight != root->_balanceFactor){ return false; } return abs(rightHeight - leftHeight) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right); } public: int Height(AVLTreeNode* root) { if (root == NULL) return 0; int leftHeight = Height(root->_left); int rightHeight = Height(root->_right); return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; } bool IsBalance() { return _IsBalance(_root); }
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log (N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
// AVLTree.h #pragma once #include <utility> #include <iostream> #include <assert.h> using namespace std; template<class K, class V> class AVLTreeNode { public: AVLTreeNode<K, V>* _left; AVLTreeNode<K, V>* _right; AVLTreeNode<K, V>* _parent; pair<K, V> _keyValue; // 键值对 int _balanceFactor; // 平衡因子 AVLTreeNode(const pair<K, V>& keyValue) :_left(nullptr) , _right(nullptr) , _parent(nullptr) , _keyValue(keyValue) , _balanceFactor(0) {} }; template<class K, class V> class AVLTree { typedef AVLTreeNode<K, V> AVLTreeNode; private: AVLTreeNode* _root; private: void RotateR(AVLTreeNode* parent) { AVLTreeNode* cur = parent->_left; AVLTreeNode* curR = cur->_right; // 对于parent的parent也要进行连接 AVLTreeNode* parentParent = parent->_parent; parent->_left = curR; // curR可能为空,因此需要对其进行空指针的判断 if (curR != nullptr) { curR->_parent = parent; } cur->_right = parent; parent->_parent = cur; // 如果parent是根节点就不需要连接,将cur作为根 if (parent == _root) { _root = cur; _root->_parent == nullptr; } // 将cur作为根,否则就需要找出链接的子树,并完成相互链接 else { if (parentParent->_left == parent) { parentParent->_left = cur; cur->_parent = parentParent; } else { parentParent->_right = cur; cur->_parent = parentParent; } } // 平衡因子 cur->_balanceFactor = parent->_balanceFactor = 0; } void RotateL(AVLTreeNode* parent) { AVLTreeNode* cur = parent->_right; AVLTreeNode* curL = cur->_left; AVLTreeNode* parentParent = parent->_parent; parent->_right = curL; if (curL != nullptr) { curL->_parent = parent; } parent->_parent = cur; cur->_left = parent; if (parent == _root) { _root = cur; _root->_parent = nullptr; } else { if (parentParent->_left == parent) { parentParent->_left = cur; cur->_parent = parentParent; } else { parentParent->_right = cur; cur->_parent = parentParent; } } cur->_balanceFactor = parent->_balanceFactor = 0; } void RotateLR(AVLTreeNode* parent) { AVLTreeNode* cur = parent->_left; AVLTreeNode* curR = cur->_right; int bf = curR->_balanceFactor; RotateL(cur); RotateR(parent); if (bf == 1) { parent->_balanceFactor = curR->_balanceFactor = 0; cur -> _balanceFactor = -1; } else if (bf == -1) { cur -> _balanceFactor = curR->_balanceFactor = 0; parent -> _balanceFactor = 1; } else if (bf == 0){ cur -> _balanceFactor = curR->_balanceFactor = parent->_balanceFactor = 0; } else { assert(false); } } void RotateRL(AVLTreeNode* parent) { AVLTreeNode* cur = parent->_right; AVLTreeNode* curR = cur->_left; int bf = curR->_balanceFactor; RotateR(cur); RotateL(parent); if (bf == -1) { parent->_balanceFactor = curR->_balanceFactor = 0; cur -> _balanceFactor = 1; } else if (bf == 1) { cur -> _balanceFactor = curR->_balanceFactor = 0; parent -> _balanceFactor = -1; } else if (bf == 0) { cur->_balanceFactor = curR->_balanceFactor = parent->_balanceFactor = 0; } else { assert(false); } } void _InOrder(AVLTreeNode* root) { if (root == NULL) return; _InOrder(root->_left); cout << root->_keyValue.first << ":" << root->_keyValue.second << endl; _InOrder(root->_right); } bool _IsBalance(AVLTreeNode* root) { if (root == NULL) return true; // 对当前树进行检查 int leftHeight = Height(root->_left); int rightHeight = Height(root->_right); if (rightHeight - leftHeight != root->_balanceFactor){ //cout << "发生错误:"; //cout << root->_keyValue.first << " 的平衡因子是:" << root->_balanceFactor // << " 应该是:" << rightHeight - leftHeight << endl; return false; } return abs(rightHeight - leftHeight) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right); } public: AVLTree() :_root(nullptr) {} bool Insert(const pair<K, V>& kv) { if (_root == nullptr) { _root = new AVLTreeNode(kv); return true; } // 根据二叉搜索树的规则寻找插入位置 AVLTreeNode* parent = nullptr; AVLTreeNode* cur = _root; while (cur) { if (cur->_keyValue.first > kv.first) { parent = cur; cur = cur->_left; } else if (cur->_keyValue.first < kv.first) { parent = cur; cur = cur->_right; } else { return false; } } // 插入新节点 cur = new AVLTreeNode(kv); if (parent->_keyValue.first > cur->_keyValue.first) { parent->_left = cur; cur->_parent = parent; } else { parent->_right = cur; cur->_parent = parent; } // 更新平衡因子 while (parent) { //根据cur插入的位置,对于parent的平衡因子进行更新, //当插入在parent的左子树时,balanceFactor需要减少, //插入在右子树时需要增加balanceFactor if (parent->_left == cur) { parent->_balanceFactor--; } else { parent->_balanceFactor++; } if (parent->_balanceFactor == 0) { break; } // 继续往上更新 else if (parent->_balanceFactor == 1 || parent->_balanceFactor == -1) { cur = parent; parent = parent->_parent; } else if (parent->_balanceFactor == 2 || parent->_balanceFactor == -2) { // 对于AVL树平衡因子异常处理 if (parent->_balanceFactor == -2 && cur->_balanceFactor == -1) { RotateR(parent); } else if (parent->_balanceFactor == 2 && cur->_balanceFactor == 1) { RotateL(parent); } else if (parent->_balanceFactor == -2 && cur->_balanceFactor == 1) { RotateLR(parent); } else if (parent->_balanceFactor == 2 && cur->_balanceFactor == -1) { RotateRL(parent); } else { assert(false); } break; } else { // 说明插入更新平衡因子之前,树中平衡因子就有问题了 assert(false); } } return true; } void InOrder() { _InOrder(_root); } int Height(AVLTreeNode* root) { if (root == NULL) return 0; int leftHeight = Height(root->_left); int rightHeight = Height(root->_right); return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; } bool IsBalance() { return _IsBalance(_root); } };
补充:
- 代码将会放到:C++/C/数据结构代码链接 ,欢迎查看!
- 欢迎各位点赞、评论、收藏与关注,大家的支持是我更新的动力,我会继续不断地分享更多的知识!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。