目录
[Part1. AVL树相关成员介绍](#Part1. AVL树相关成员介绍)
[Part1.1. AVL树节点相关](#Part1.1. AVL树节点相关)
[Part1.2. AVL树本体](#Part1.2. AVL树本体)
[Part2. 四大旋转操作](#Part2. 四大旋转操作)
[Part2.1. 左单旋](#Part2.1. 左单旋)
[Part2.2. 右单旋](#Part2.2. 右单旋)
[Part2.3. 左右双旋](#Part2.3. 左右双旋)
[Part2.4. 右左双旋](#Part2.4. 右左双旋)
[Part3. 平衡因子作用解析](#Part3. 平衡因子作用解析)
[Part3.1. 平衡因子与旋转](#Part3.1. 平衡因子与旋转)
[Part3.2. 平衡因子与向上调整(插入)](#Part3.2. 平衡因子与向上调整(插入))
[Part4. 插入操作](#Part4. 插入操作)
[Part5. 判断操作](#Part5. 判断操作)
[Part6. Find接口](#Part6. Find接口)
[Part7. 删除操作](#Part7. 删除操作)
[Part7.1. 平衡因子与向上调整(删除)](#Part7.1. 平衡因子与向上调整(删除))
[Part7.2. 删除的旋转操作](#Part7.2. 删除的旋转操作)
[Part7.3. 删除的实现](#Part7.3. 删除的实现)
[Part8. 反思与总结](#Part8. 反思与总结)
[Part9. 代码分享](#Part9. 代码分享)
[Part9.1. AVL实现](#Part9.1. AVL实现)
[Part9.2. 测试](#Part9.2. 测试)
[Part10. 结语](#Part10. 结语)
前言
前置知识:【知识讲解】 二叉搜索树的相关知识与实现-CSDN博客
AVL树作为经典的数据结构之一,他的有些接口会稍显复杂,接下来跟随小编的视角来剖析一下它的全部吧。
let's go!!!!!!!!!
Part1. AVL树相关成员介绍
我们先来看构成AVL树的成员是哪些,这方便我们去介绍后续的内容,我们来看:
Part1.1. AVL树节点相关
我们先来看代码:
cpptemplate<class k,class v> struct AVLTreeNode { std::pair<k, v> _kv; AVLTreeNode<k, v>* _left; AVLTreeNode<k, v>* _right; AVLTreeNode<k, v>* _parent; int _bf; AVLTreeNode(const std::pair<k, v>& kv) :_kv(kv) , _left(nullptr) , _right(nullptr) , _parent(nullptr) , _bf(0) { } };
我们来对各个成员详细介绍一下:
<1> _kv:存储数据的地方(key、value)。
<2> _left、_right:当前节点的左右节点。
<3> _parent:当前节点的父亲节点。
<4> _bf:平衡因子,用于衡量当前节点左右子树的情况(_bf=当前节点的右子树高度减去其左子树高度)(插入操作时,插到左子树_bf--,反之++)(后续调整旋转的核心)
Part1.2. AVL树本体
这个就比较简洁了,我们来看代码:
cpptypedef AVLTreeNode<k, v> Node; Node* _root = nullptr;
通过一个_root来统一管理整棵树。
Part2. 四大旋转操作
虽然这个按道理来说应该放到后面来讲,但是把这个单拿出来详细讲解会对后面的的插入删除有很大的帮助,毕竟这个就是整个AVL树的核心。
四大旋转操作分为左单旋、右单旋、左右双旋、右左双旋,我们来分别看看吧:
Part2.1. 左单旋
代码实现:
cppvoid RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; subR->_left = parent; parent->_right = subRL; if (subRL != nullptr)//当h==0的情况 { subRL->_parent = parent; } subR->_parent = parent->_parent;//记得还要更新_parent if (parent == _root) { _root = subR; } else { if (parent->_parent->_left == parent) { parent->_parent->_left = subR; } else { parent->_parent->_right = subR; } } parent->_parent = subR; parent->_bf = 0;//更新平衡因子(根据图片上来看) subR->_bf = 0; }
Part2.2. 右单旋
代码实现:
cppvoid RotateR(Node* parent)//逻辑同上 { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; subL->_right = parent; if (subLR != nullptr) { subLR->_parent = parent; } subL->_parent = parent->_parent; if (parent == _root) { _root = subL; } else { if (parent->_parent->_left == parent) { parent->_parent->_left = subL; } else { parent->_parent->_right = subL; } } parent->_parent = subL; subL->_bf = 0; parent->_bf = 0; }
Part2.3. 左右双旋
cppvoid RotateLR(Node* parent) { Node* subL = parent->_left; int bf = subL->_right->_bf; RotateL(subL); RotateR(parent); if (bf == 1)//更新平衡因子 //由于上面左旋右旋他对于平衡因子都会置为0 估值要修改不是0的就可以 { subL->_bf = -1; } else if (bf == -1) { parent->_bf = 1; } else if (bf == 0) { ; } else { assert(false); } }
为什么这里要先采取一个左旋再来右旋呢?首先,这个场景下,是由于左边太'重'才导致的不平衡,因此我们要把左边的节点掰到右边去,这样才可以达到平衡,但是这个场景又不像上面的右单旋,直接右单旋是解决不了问题的。我们可以对比一下两者有什么区别,两者的区别就在于,右单旋在B这个子树中,他的重量是全部都在左边,左右单旋这个场景他是重在右边,因此我们要先用左单旋把重量先移到左边,让这个场景变成适用右单旋的场景,再用右单旋解决问题。
这里的平衡因子的更新也是充满细节,我们来看:
也就是说新增节点具体在左子树,右子树,或者自身作为叶子结点的时候,平衡因子的更新是有区别的,我们可以直接依靠看c结点的_bf来判断是加到哪了,从而来更新上面的平衡因子。
Part2.4. 右左双旋
cppvoid RotateRL(Node* parent)//逻辑同上 { Node* subR = parent->_right; int bf = subR->_left->_bf; RotateR(subR); RotateL(parent); if (bf == 1) { parent->_bf = -1; } else if (bf == -1) { subR->_bf = 1; } else if (bf == 0) { ; } else { assert(false); } }
Part3. 平衡因子作用解析
上面我们这么大费周章的修改平衡因子是为了什么?他的这个作用又是什么?实际上我们可以只通过两个结点的_bf就可以知道我们用哪种旋转了,为什么?我们来看:
Part3.1. 平衡因子与旋转
旋转什么时候发生?当这个节点不平衡的时候,我们具体要对应与哪种旋转呢?我们来看:
<1> 当A处的_bf为2时候,说明右边节点高度高,则我们在最后一定要右旋,但是在那之前具体要左旋来调整还是不要就要具体来看了,具体到哪看?自然是到右边去看(因为是右子树比较重),看什么?看C处的_bf值,如果为1,说明新加的节点是在C的右边,也就是纯粹的右边重,也就是只需要一个整体的右旋就可以解决。反之,要是为-1,说明新加的是在左边,就要先局部左旋将整体调整为右旋的模型,在整体右旋解决。
<2> A处为-2逻辑同上。
也就是说,我们在遇到不平衡的节点时,我们根据++他是2还是-2,具体在到出问题的那边子树,在看那边的_bf值判断调用哪种旋转调整++。
Part3.2. 平衡因子与向上调整(插入)
当我们A处的_bf不为2、-2时候怎么办?什么时候我们要继续向上更新?这里更新的依据是什么?旋转后怎么办,还要继续向上调整吗?针对这些问题我们来看:
首先,我们是否向上调整关键在于我们一系列操作是否改变了树的高度,我们来看上面的图,我们修改的这个树也是上面的结点的子树,当我们调整的这个树高度改变,那么在上面看来就是它的一颗左子树与其右子树之间的高度差改变,也就是我们要继续向上调整了。
那平衡因子是怎么昭示高度改变呢?我们来看:
<1> 调整后_bf为0,说明原本其为1/-1,即就是原本它的子树有一个高,现在相当于加到那个比较矮的子树上了,相当于平衡了,高度不变,不用向上调整。
<2> 调整后_bf为1/-1,说明原本为0(不可能是2/-2,不符合AVL树,说明之前出问题了),0就说明之前是平衡的,现在1/-1,一边子树高了,高度改变,要向上调整。
为什么在插入操作流程旋转不用继续上向上调整了,我们来看:
我们可以数一数,调整前高度为h+2,调整后也是,对于上面四个都是,自然不用继续调整。
前置知识差不多了,我们来看插入和删除吧:
Part4. 插入操作
我们来直接看代码:
cppbool insert(const std::pair<k, v>& kv) { if (_root == nullptr) { _root = new Node(kv); return true; } Node* cur = _root; Node* parent = nullptr; while (cur) { if (cur->_kv.first > kv.first) { parent = cur; cur = cur->_left; } else if(cur->_kv.first < kv.first) { parent = cur; cur = cur->_right; } else { return false; } } cur = new Node(kv); if (parent->_kv.first > kv.first) { parent->_left = cur; } else { parent->_right = cur; } cur->_parent = parent; //前面这些都是二叉平衡树的插入流程 不熟悉的可以看我前面博客 while (parent)//当parent为nullptr自动退出 { if (cur == parent->_left)//根据新加入的节点更新 { parent->_bf--; } else { parent->_bf++; } if (parent->_bf == 0)//为0不用向上更新 退出 { break; } else if (parent->_bf == 1 || parent->_bf == -1)//1/-1继续向上更新 { cur = parent; parent = parent->_parent; } else if(parent->_bf == 2 || parent->_bf == -2)//根据_bf来决定用什么旋转 { if (parent->_bf == -2 && cur->_bf == -1) { RotateR(parent); } else if (parent->_bf == 2 && cur->_bf == 1) { RotateL(parent); } else if (parent->_bf == -2 && cur->_bf == 1) { RotateLR(parent); } else if (parent->_bf == 2 && cur->_bf == -1) { RotateRL(parent); } else { assert(false); } break; } else { assert(false); } } return true; }
插入操作就是综合前面的知识来,还结合了之前二叉搜索树的插入操作,接下来,我们来看看其他的接口吧。
Part5. 判断操作
我们来看代码:
cppvoid IsAVLTree()const { if (_IsAVLTree(_root) == -1) { cout << "NO" << endl; } else { cout << "YES" << endl; } } int _IsAVLTree(Node* root)const//利用后序遍历 从叶子节点往上判断 时间复杂度为O(N) { if (root == nullptr) { return 0; } int left = _IsAVLTree(root->_left); int right = _IsAVLTree(root->_right); if (left == -1 || right == -1) { return -1; } if (abs(left - right) > 1 || root->_bf != right - left)//检查左右高度差以及_bf是否满足条件 { return -1; } if (left > right) { right = left; } return right + 1; }
Part6. Find接口
我们来看代码:
cppNode* Find(const k& key)//与二叉搜索树一致 { Node* cur = _root; while (cur != nullptr) { if (cur->_kv.second > key) { cur = cur->_left; } else if (cur->_kv.second < key) { cur = cur->_right; } else { return cur; } } return nullptr; }
Part7. 删除操作
删除的逻辑大致上与上面插入相似,不过在一些地方还是有所区别,我们来看看吧:
Part7.1. 平衡因子与向上调整(删除)
删除不与插入相似,对于相同的平衡因子数值,两者的操作会不一样,来看看吧:
<1> 当_bf为0时,说明之前的_bf数值为1或者-1,也就是说之前会有一棵子树的高度高于另一棵,现在删去了多的那一个,导致高度改变,需要向上调整。
<2> 当_bf为1/-1时,说明之前为0,现在是在两个高度相同的子树中的一个删去了,不影响树的整体高度,故直接停止向上调整。
<3> 当_bf为2/-2时,旋转调整,这个与插入有很大区别,来具体看看吧。
Part7.2. 删除的旋转操作
当A处的_bf为2,C处为1时,我们来看旋转的过程:
由于高度改变了,所以在旋转后还要向上调整,对于插入的四个情况是这样的,但是这里与上面的模型不同,b、c的_bf还可以为0,我们来看看当其为0的时候是什么情况:
所以在这种情况下,不用向上调整了,但要注意调整A、B的_bf。
Part7.3. 删除的实现
我们来看代码:
cppvoid erase(const k& key) { Node* cur = Find(key);//找到节点 if (cur == nullptr) return; Node* parent = cur->_parent; int curn = 0;//用这个来标记上次处理是在左子还是右子 if (cur->_left == nullptr&&cur->_right!=nullptr)//这些与二叉搜索树相似 { if (cur == _root) { _root = cur->_right; _root->_parent = nullptr; delete cur; return; } if (parent->_left == cur) { parent->_left = cur->_right; cur->_right->_parent = parent; curn = -1; } else { parent->_right = cur->_right; cur->_right->_parent = parent; curn = 1; } delete cur; } else if (cur->_left != nullptr && cur->_right == nullptr) { if (cur == _root) { _root = cur->_left; _root->_parent = nullptr; delete cur; return; } if (parent->_left == cur) { parent->_left = cur->_left; cur->_left->_parent = parent; curn = -1; } else { parent->_right = cur->_left; cur->_left->_parent = parent; curn = 1; } delete cur; } else if (cur->_left == nullptr && cur->_right == nullptr) { if (cur == _root) { _root = nullptr; delete cur; return; } if (parent->_left == cur) { parent->_left = nullptr; curn = -1; } else { parent->_right = nullptr; curn = 1; } delete cur; } else { Node* replace = cur->_right; while (replace->_left != nullptr) { replace = replace->_left; } parent = replace->_parent; cur->_kv = replace->_kv; if (parent->_left == replace) { parent->_left = replace->_right; if(replace->_right!=nullptr) replace->_right->_parent = parent; curn = -1; } else { parent->_right = replace->_right; if (replace->_right != nullptr) replace->_right->_parent = parent; curn = 1; } delete replace; } cur = parent->_left; if (curn == 1) { cur = parent->_right; } while (parent) { if (curn==-1)//更新_bf { parent->_bf++; } else { parent->_bf--; } if (parent->_bf == 1 || parent->_bf == -1)//为1/-1时候退出向上调整 { break; } else if (parent->_bf == 0)//为0的时候更新_bf { cur = parent; parent = parent->_parent; if (parent == nullptr) { break; } curn = 1; if (cur == parent->_left) { curn = -1; } } else if (parent->_bf == -2)//依靠_bf来旋转 { cur = parent->_left; if (cur->_bf == -1) { RotateR(parent); parent = cur->_parent; } else if(cur->_bf == 1) { RotateLR(parent); parent = parent->_parent->_parent; cur = cur->_parent; } else//为0的时候 { RotateR(parent); parent->_bf = -1; cur->_bf = 1; return; } if (parent == nullptr) { break; } curn = 1; if (cur == parent->_left) { curn = -1; } } else if (parent->_bf == 2) { cur = parent->_right; if (cur->_bf == 1) { RotateL(parent); parent = cur->_parent; } else if (cur->_bf == -1) { RotateRL(parent); parent = parent->_parent->_parent; cur = cur->_parent; } else { RotateL(parent); parent->_bf = 1; cur->_bf = -1; return; } if (parent == nullptr) { break; } curn = 1; if (cur == parent->_left) { curn = -1; } } } }
Part8. 反思与总结
通过上面的一系列过程,其实可以认识到一个结论,那就是整个调整的过程就是依靠平衡因子来实现的,建立模型是为了具象化平衡因子带来的影响,以及在调整后高度是否改变从而来影响是否继续向上调整。所以++平衡因子是全局的关键。++
Part9. 代码分享
Part9.1. AVL实现
cpp#pragma once #include<iostream> #include<assert.h> #include<algorithm> #include<cstdlib> #include<vector> using namespace std; template<class k,class v> struct AVLTreeNode { std::pair<k, v> _kv; AVLTreeNode<k, v>* _left; AVLTreeNode<k, v>* _right; AVLTreeNode<k, v>* _parent; int _bf; AVLTreeNode(const std::pair<k, v>& kv) :_kv(kv) , _left(nullptr) , _right(nullptr) , _parent(nullptr) , _bf(0) { } }; template<class k,class v> class AVLTree { typedef AVLTreeNode<k, v> Node; public: AVLTree() = default; bool insert(const std::pair<k, v>& kv) { if (_root == nullptr) { _root = new Node(kv); return true; } Node* cur = _root; Node* parent = nullptr; while (cur) { if (cur->_kv.first > kv.first) { parent = cur; cur = cur->_left; } else if(cur->_kv.first < kv.first) { parent = cur; cur = cur->_right; } else { return false; } } cur = new Node(kv); if (parent->_kv.first > kv.first) { parent->_left = cur; } else { parent->_right = 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) { RotateR(parent); } else if (parent->_bf == 2 && cur->_bf == 1) { RotateL(parent); } else if (parent->_bf == -2 && cur->_bf == 1) { RotateLR(parent); } else if (parent->_bf == 2 && cur->_bf == -1) { RotateRL(parent); } else { assert(false); } break; } else { assert(false); } } return true; } void erase(const k& key) { Node* cur = Find(key); if (cur == nullptr) return; Node* parent = cur->_parent; int curn = 0; if (cur->_left == nullptr&&cur->_right!=nullptr) { if (cur == _root) { _root = cur->_right; _root->_parent = nullptr; delete cur; return; } if (parent->_left == cur) { parent->_left = cur->_right; cur->_right->_parent = parent; curn = -1; } else { parent->_right = cur->_right; cur->_right->_parent = parent; curn = 1; } delete cur; } else if (cur->_left != nullptr && cur->_right == nullptr) { if (cur == _root) { _root = cur->_left; _root->_parent = nullptr; delete cur; return; } if (parent->_left == cur) { parent->_left = cur->_left; cur->_left->_parent = parent; curn = -1; } else { parent->_right = cur->_left; cur->_left->_parent = parent; curn = 1; } delete cur; } else if (cur->_left == nullptr && cur->_right == nullptr) { if (cur == _root) { _root = nullptr; delete cur; return; } if (parent->_left == cur) { parent->_left = nullptr; curn = -1; } else { parent->_right = nullptr; curn = 1; } delete cur; } else { Node* replace = cur->_right; while (replace->_left != nullptr) { replace = replace->_left; } parent = replace->_parent; cur->_kv = replace->_kv; if (parent->_left == replace) { parent->_left = replace->_right; if(replace->_right!=nullptr) replace->_right->_parent = parent; curn = -1; } else { parent->_right = replace->_right; if (replace->_right != nullptr) replace->_right->_parent = parent; curn = 1; } delete replace; } cur = parent->_left; if (curn == 1) { cur = parent->_right; } while (parent) { if (curn==-1) { parent->_bf++; } else { parent->_bf--; } if (parent->_bf == 1 || parent->_bf == -1) { break; } else if (parent->_bf == 0) { cur = parent; parent = parent->_parent; if (parent == nullptr) { break; } curn = 1; if (cur == parent->_left) { curn = -1; } } else if (parent->_bf == -2) { cur = parent->_left; if (cur->_bf == -1) { RotateR(parent); parent = cur->_parent; } else if(cur->_bf == 1) { RotateLR(parent); parent = parent->_parent->_parent; cur = cur->_parent; } else { RotateR(parent); parent->_bf = -1; cur->_bf = 1; return; } if (parent == nullptr) { break; } curn = 1; if (cur == parent->_left) { curn = -1; } } else if (parent->_bf == 2) { cur = parent->_right; if (cur->_bf == 1) { RotateL(parent); parent = cur->_parent; } else if (cur->_bf == -1) { RotateRL(parent); parent = parent->_parent->_parent; cur = cur->_parent; } else { RotateL(parent); parent->_bf = 1; cur->_bf = -1; return; } if (parent == nullptr) { break; } curn = 1; if (cur == parent->_left) { curn = -1; } } } } void InOrder()const { _InOredr(_root); cout << endl; } void IsAVLTree()const { if (_IsAVLTree(_root) == -1) { cout << "NO" << endl; } else { cout << "YES" << endl; } } size_t Height()const { return _Height(_root); } size_t Size()const { return _Size(_root); } Node* Find(const k& key) { Node* cur = _root; while (cur != nullptr) { if (cur->_kv.second > key) { cur = cur->_left; } else if (cur->_kv.second < key) { cur = cur->_right; } else { return cur; } } return nullptr; } private: void RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; subL->_right = parent; if (subLR != nullptr) { subLR->_parent = parent; } subL->_parent = parent->_parent; if (parent == _root) { _root = subL; } else { if (parent->_parent->_left == parent) { parent->_parent->_left = subL; } else { parent->_parent->_right = subL; } } parent->_parent = subL; subL->_bf = 0; parent->_bf = 0; } void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; subR->_left = parent; parent->_right = subRL; if (subRL != nullptr) { subRL->_parent = parent; } subR->_parent = parent->_parent; if (parent == _root) { _root = subR; } else { if (parent->_parent->_left == parent) { parent->_parent->_left = subR; } else { parent->_parent->_right = subR; } } parent->_parent = subR; parent->_bf = 0; subR->_bf = 0; } void RotateLR(Node* parent) { Node* subL = parent->_left; int bf = subL->_right->_bf; RotateL(subL); RotateR(parent); if (bf == 1) { subL->_bf = -1; } else if (bf == -1) { parent->_bf = 1; } else if (bf == 0) { ; } else { assert(false); } } void RotateRL(Node* parent) { Node* subR = parent->_right; int bf = subR->_left->_bf; RotateR(subR); RotateL(parent); if (bf == 1) { parent->_bf = -1; } else if (bf == -1) { subR->_bf = 1; } else if (bf == 0) { ; } else { assert(false); } } void _InOredr(Node* root)const { if (root == nullptr) return; _InOredr(root->_left); cout << root->_kv.second << ":"<<root->_kv.second<<endl; _InOredr(root->_right); } int _IsAVLTree(Node* root)const { if (root == nullptr) { return 0; } int left = _IsAVLTree(root->_left); int right = _IsAVLTree(root->_right); if (left == -1 || right == -1) { return -1; } if (abs(left - right) > 1 || root->_bf != right - left) { return -1; } if (left > right) { right = left; } return right + 1; } int _Height(Node* root)const { if (root == nullptr) return 0; int leftH = _Height(root->_left); int rightH = _Height(root->_right); if (leftH > rightH) { rightH = leftH; } return rightH + 1; } int _Size(Node* root)const { if (root == nullptr) return 0; int leftSize = _Size(root->_left); int rightSize = _Size(root->_right); return leftSize + rightSize+1; } Node* _root = nullptr; };
Part9.2. 测试
cpp#include"头.h" void test() { const int N = 1000; vector<int> arr; arr.reserve(N); srand(time(0)); for (int i = 0; i < N; i++) { arr.push_back(rand()+i); } //int arr[] = { 54,0,55,16,5,4,29,38,22,14 }; AVLTree<int, int> aaa; for (auto e : arr) { aaa.insert({e,e}); } for (auto e : arr) { aaa.erase(e); aaa.InOrder(); aaa.IsAVLTree(); cout << "--------------------------------------------------------------" << endl; } } int main() { test(); }
Part10. 结语
这篇文章我们认识到了AVL树的实现,接下来,小编也会带来更复杂的红黑树的讲解,敬请期待~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~











