一.平衡树的介绍
平衡树是以二叉树结构为基础,同时引入了平衡因子进行了限制,以保证树的结点之间的高度差小于等于1,在插入删除结点时通过旋转的方法保持高度相对平衡,从而提高搜索等效率。
二.代码实现
1.平衡树结点
平衡树结点是以二叉树为基础构建的,因此需要左右结点指针left与right。传入pair一部分为值value另一部分为关键字key,以及平衡因子bf(左子树高度减右子树高度)。
下面是结点代码:
cpptemplate<class K, class V> struct AVLTreeNode { pair<K, V> _kv; AVLTreeNode<K, V>* _left; AVLTreeNode<K, V>* _right; AVLTreeNode<K, V>* _parent; int _bf; AVLTreeNode(const pair<K, V>& kv) :_kv(kv) , _left(nullptr) , _right(nullptr) , _parent(nullptr) , _bf(0) {} };
2.平衡树的插入
插入结点时首先要找到合适的位置空间来放置新结点,所以我们需要遍历树。从根结点开始(cur),若值比当前结点小让cur往左边走,若值比当前结点大让cur往右边走,直到cur跑到空时停止。
接着进行判断,根据结点高度差的不同,结点位置不一样进行不同的旋转方式,调整相应的平衡因子,根据parent结点的高度决定是否要继续向上更新。
下面是寻找结点的相应代码:
cppif (_root == nullptr) { _root = new Node(kv); return true; } Node* parent = nullptr; Node* cur = _root; while (cur) { if (cur->_kv.first < kv.first) { parent = cur; cur = cur->_right; } else if (cur->_kv.first > kv.first) { parent = cur; cur = cur->_left; } else { return false; } } cur = new Node(kv); if (parent->_kv.first < kv.first) { parent->_right = cur; } else { parent->_left = cur; } cur->_parent = parent;
若树的结点为空那么直接插入根结点,若不为空按照上述的方法遍历到空结点后,需要注意要确定parent的位置。
下面我们来讨论一下旋转
首先计算平衡因子,若cur在parent的左边则bf--,若cur在parent的右边则bf++,若parent的bf为0时直接跳出结果。
若parent的左右都存在结点,那么就是平衡的,反之则不平衡,若bf等于1或者-1,时继续向上更新bf平衡值,让cur等于parent,parent等于parent的parent。
向上遍历若得到bf等于2或者-2时,说明此时树已经不平衡了,需要进行旋转调整树的结构。
下面我们依次分析左旋,右旋,左右双旋,右左双旋的情况
1.右单旋
当parent的平衡因子为-2,cur的平衡因子为-1时我们需要右单旋
如图p代表parent,c代表cur此时我们需要进行右旋操作d代表插入的结点。以parent为轴向右旋转让cur变成parent。
我们需要得到parent结点,cur结点(SUL),以及cur结点的右结点(SULR)。
让SULR变成parent的左结点SUL的右结点变成parent,其他的不进行改变。
需要注意的是,首先需要判断SULR结点是否存在,若存在就让其变为parent的左结点,若不存在就不进行操作。以及我们需要找到parent结点的parent结点,若parentparent结点为空则则让root结点直接变为SUL即可,若不为空需要判断parent结点是parentparent结点的左还是右结点,然后将其左或右给给SUL。
下面放上代码:
cppvoid RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if (subLR) subLR->_parent = parent; Node* parentParent = parent->_parent; subL->_right = parent; parent->_parent = subL; //parent有可能是整棵树的根,也有可能是局部子树 //如果是整棵树的根则修改root // if (parentParent == nullptr) { _root = subL; subL->_parent = nullptr; } else { if (parent == parentParent->_left) { parentParent->_left = subL; } else { parentParent->_right = subL; } subL->_parent = parentParent; } parent->_bf = subL->_bf = 0; }
2.左单旋
左单旋与右单旋类似,只是parent结点的左右子树互换了位置。
当parent的bf为2时,cur的bf为1时,需要进行左旋操作。
如图p代表parent ,c代表cur结点,插入的结点为a。以parent为轴向左旋转。
我们需要得到cur结点的左结点(SURL),parent结点以及cur结点(SUR)。让parent的右为SURL,SUR的左为parent。其他的不进行改变。
与上文相同需要找到parent的parent,若parentparent不存在就将root结点设置为SUR,若存在,则根据parent是parentparent的左或者右结点来分配给SUR结点。
下面是代码:
cppvoid RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; parent->_right = subRL; if (subRL) subRL->_parent = parent; Node* parentParent = parent->_parent; subR->_left = parent; parent->_parent = subR; if (parentParent == nullptr) { _root = subR; subR->_parent = nullptr; } else { if (parent == parentParent->_left) { parentParent->_left = subR; } else { parentParent->_right = subR; } subR->_parent = parentParent; } parent->_bf = subR->_bf = 0; }
3.左右双旋
当parent的bf等于-2,cur的bf等于1时,进行左右双旋。先对cur进行左旋让其bf等于-1,此时就回到了第一种情况,再对parent进行一次右旋就完成了操作。
当新增结点cur结点与parent结点形成一个角度时,需要进行双旋操作,如果是开口向右则进行左右双旋,若开口向左则进行右左双旋。
此处的难点在于对插入结点bf的分类讨论。
第一种情况 当SULR(R)的bf为0时
此时我们需要先对SUL进行左旋操作,使其满足三个点连成一条直线,之后再对parent进行右旋操作,得到下图。
我们可以根据最终结果直接写出代码,先对SUL进行左旋再对parent进行右旋,最后设置三个结点的bf都为0即可。
第二种情况 当SULR(R) 的bf为-1时
当我们进行左右双旋操作后
变成如图,此时需要将parent的bf设置为1,SUL和SULR设置为0。
第三种情况 当SURL(R)的bf为1时
进行左右双旋操作之后
将SUL的bf设置为-1,parent与SULR的bf设置为0即可。
下面附上代码:
cppvoid RotateLR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; int bf = subLR->_bf; RotateL(parent->_left); RotateR(parent); if (bf == 0) { subL->_bf = 0; subLR->_bf = 0; parent->_bf = 0; } else if (bf == 1) { subL->_bf = -1; parent->_bf = 0; subLR->_bf = 0; } else if (bf == -1) { subL->_bf = 0; subLR->_bf = 0; parent->_bf = 1; } else { assert(false); } }
4.右左双旋
右左双旋本质与左右双旋一样,只是左右子树位置互换
所以我就直接附上代码:
cppvoid RotateRL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf; RotateR(parent->_right); RotateL(parent); if (bf == 0) { subR->_bf = 0; subRL->_bf = 0; parent->_bf = 0; } else if (bf == 1) { subRL->_bf = 0; parent->_bf = -1; subR->_bf = 0; } else if (bf == -1) { subLR->_bf = 0; subR->_bf = 1; parent->_bf = 0; } else { assert(false); } }
5.插入代码总结
cppbool insert(const pair<K, V>& kv) { if (_root == nullptr) { _root = new Node(kv); return true; } Node* parent = nullptr; Node* cur = _root; while (cur) { if (cur->_kv.first < kv.first) { parent = cur; cur = cur->_right; } else if (cur->_kv.first > kv.first) { parent = cur; cur = cur->_left; } else { return false; } } cur = new Node(kv); if (parent->_kv.first < kv.first) { parent->_right = cur; } else { parent->_left = cur; } cur->_parent = parent; //更新平衡因子 while (parent) { //定义平衡因子左减右加 if (cur == parent->_left) parent->_bf--; else parent->_bf++; if (parent->_bf == 0) { //更新结果 break; } 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 && cur->_bf == 1)//左单旋 { RotateL(parent); } else if (parent->_bf == 2 && cur->_bf == -1)//右左双旋 { RotateRL(parent); } else if (parent->_bf == -2 && cur->_bf == 1)//左右双旋 { RotateLR(parent); } else if(parent->_bf == -2 && cur->_bf == -1)//右单旋 { RotateR(parent); } break; } else { //说明传入的有问题报错日志 assert(false); } } return true; } //右单旋 void RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if (subLR) subLR->_parent = parent; Node* parentParent = parent->_parent; subL->_right = parent; parent->_parent = subL; //parent有可能是整棵树的根,也有可能是局部子树 //如果是整棵树的根则修改root // if (parentParent == nullptr) { _root = subL; subL->_parent = nullptr; } else { if (parent == parentParent->_left) { parentParent->_left = subL; } else { parentParent->_right = subL; } subL->_parent = parentParent; } parent->_bf = subL->_bf = 0; } //左单旋 void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; parent->_right = subRL; if (subRL) subRL->_parent = parent; Node* parentParent = parent->_parent; subR->_left = parent; parent->_parent = subR; if (parentParent == nullptr) { _root = subR; subR->_parent = nullptr; } else { if (parent == parentParent->_left) { parentParent->_left = subR; } else { parentParent->_right = subR; } subR->_parent = parentParent; } parent->_bf = subR->_bf = 0; } void RotateLR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; int bf = subLR->_bf; RotateL(parent->_left); RotateR(parent); if (bf == 0) { subL->_bf = 0; subLR->_bf = 0; parent->_bf = 0; } else if (bf == 1) { subL->_bf = -1; parent->_bf = 0; subLR->_bf = 0; } else if (bf == -1) { subL->_bf = 0; subLR->_bf = 0; parent->_bf = 1; } else { assert(false); } } void RotateRL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf; RotateR(parent->_right); RotateL(parent); if (bf == 0) { subR->_bf = 0; subRL->_bf = 0; parent->_bf = 0; } else if (bf == 1) { subRL->_bf = 0; parent->_bf = -1; subR->_bf = 0; } else if (bf == -1) { subLR->_bf = 0; subR->_bf = 1; parent->_bf = 0; } else { assert(false); } }
3.键值查找 树的高度计算 与平衡树判断
查找值时,只需要遍历整个树即可,以根节点(cur)为起始点,当所找的值比cur小时向左树寻找,比cur大时向右树寻找,直到cur值等于寻找值时返回当前结点,若cur为空了仍未找到结点就返回空。
cppNode* find(const K& key) { Node* cur = _root; while (cur) { if (cur->_kv.first < key) { cur = cur->_right; } else if (cur->_kv.first > key) { cur = cur->_left; } else { return cur; } } return nullptr; }
计算树的高度时我们采用递归左右子树的方法实现,判断左右子树的大小取大值,逐层递归得到最终值。
cppint _Height(Node* root) { if (root == nullptr) return 0; int leftHeight = _Height(root->_left); int rightHeight = _Height(root->_right); return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; }
判断是否平衡时,我们可以调用左右子树的高度,然后相减,得到diff。若diff大于2说明高度异常,若根结点的bf不等于diff说明平衡因子计算存在错误。
cppbool _IsBalanceTree(Node* root) { if (nullptr == root) return true; int leftHeight = _Height(root->_left); int rightHeight = _Height(root->_right); int diff = leftHeight - rightHeight; if (abs(diff) >= 2) { cout << root->_kv.first << "高度异常" << endl; } if (root->_bf != diff) { cout << root->_kv.first << "平衡因子异常" << endl; return false; } return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right); }
三.总结
平衡树要求任意节点的左右子树高度差绝对值不超过 1,且左右子树本身也是平衡树。通过限制树的高度(保持在 O(logn) 级别),确保查找、插入、删除等操作的时间复杂度稳定在 O(logn),避免退化为链表的 O(n) 复杂度。每次插入或删除后通过旋转(左旋、右旋、左右双旋、右左双旋)立即恢复平衡。
它的优点在于时间复杂度低,适合大量数据的操作,缺点在于需要动态的更新数据进行旋转增大了时间开销。