目录
AVL树的概念
AVL树的节点结构
AVL树的插入
更新平衡节点
代码实现
AVL树的旋转
左单旋
右单旋
左右双旋
右左双旋
AVL树的删除
AVL树的查找
AVL树的高度
AVL树的判定
AVL树的遍历
AVL树的概念
二叉排序(搜索)树,虽然可以缩短查找的效率,但是在数据接近有序的时候,二叉排序树会退化成为单支树(斜树),相当于在顺序表中查找元素,效率低下。
因此,两位俄罗斯的数学家发明了一种解决上述问题的办法,向二叉搜索树插入新的节点后,通过调整保证左右子树的高度差绝对值不大于1,从而降低树的高度,减少平均搜索长度。
平衡二叉树(Balanced Binary Tree)是一种特殊的二叉排序树,又称AVL树。
AVL树的性质:
1.AVL树是二叉排序树
2.左右子树高度之差(即平衡因子)的绝对值不超过1,即平衡因子可能为-1,0,1,
AVL树的节点结构
用KV模型定义二叉树,把节点定义成三叉链结构,因为构造的新节点左右子树都为空树,所以平衡因子设为0
平衡因子:右子树的高度-左子树的高度
template<class K,class V>
struct AVLTreeNode
{//存储的键值对pair<K, V> _kv;//平衡因子(balance factor)int _bf;//存储的三叉链AVLTreeNode<K, V>* _parent;//方便找到父节点AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;//构造函数AVLTreeNode(const pair<K, V>& kv):_kv(kv), _parent(nullptr), _left(nullptr), _right(nullptr), _bf(0){}
};
AVL树的插入
更新平衡节点
AVL树插入结点时有以下三个步骤:
1.按照二叉搜索树的插入方法,找到待插入位置。
2.找到待插入位置后,将待插入结点插入到树中。
3.更新平衡因子,如果出现不平衡,则需要进行旋转。
与二叉搜索树相比,多了更新平衡因子这个步骤。因为插入节点后树的高度可能会变化
更新父节点的平衡因子(一定要更新)
1.左边插入:parent的平衡因子--
2.右边插入:parent的平衡因子++
根据父节点的平衡因子,判断是否要更新祖先节点的平衡因子
1.如果父节点bf==0,说明父节点原来一定是-1或+1,插入的节点弥补了原来的空缺,树的高度没有改变,不用向上更新
2.如果为+-1,说明平衡因子从0变成了-1,+1,高度改变,所以要向上更新
3.如果为+-2,说明该父节点就是最小不平衡子树的根节点,不用再向上查找
代码实现
class AVLTree
{typedef AVLTreeNode<K, V> Node;
private:Node* _root;public:AVLTree():_root(nullptr){}bool Insert(const 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 (cur->_kv.first < parent->_kv.first) {parent->_left = cur;cur->_parent = parent;parent->_bf--;}else{parent->_right = cur;cur->_parent = parent;parent->_bf++;}while (parent)//最远要更新到根节点{if (parent->_bf == 0) {break;}else if (abs(parent->_bf) == 1){cur = cur->_parent;parent = parent->_parent;//向上查找}else if(abs(parent->_bf)==2){//判断是哪一种旋转情况if (parent->_bf==2&&cur->bf==1){rotateL(parent);}else if (parent->_bf == -2 && cur->bf == -1){rotateR(parent);}else if (parent->_bf == -2 && cur->bf == 1){rotateLR(parent);}else if (parent->_bf == 2 && cur->bf == -1){rotateRL(parent);}break;}else{//如果程序走到这里,说明在此之前就有不平衡的子树assert(false);}}return true;}
};
AVL树的旋转
左单旋
什么时候需要用到左旋操作呢?
当 parent 的平衡因子为 2,cur 的平衡因子为 1 时,需要进行左单旋。
并且经过左单旋后,树的高度没有发生变化,所以左单旋后无需继续往上更新平衡因子。
步骤:
- 先让 subR 的左子树(subRL)作为 parent 的右子树。
- 然后让 parent 作为 subR 的左子树。
- 接下来让 subR 作为整个子树的根。
- 最后更新平衡因子。
代码实现
void rotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;//1.建立subRL与parent之间的关系//左子树滑动parent->_right = subRL;if (subRL) {subRL->_parent = parent;}//2.建立subR和parent之间的关系//更新右子树的左子树subR->_left = parent;parent->_parent = subR;//3.建立pparent和subR之间的关系//与上一个节点建立连接if (parent == _root){_root = subR;subR->_parent == nullptr;}else{subR->_parent = pparent;if (parent = pparent->_left) {pparent->_left = subR;}else{pparent->_right = subR;}}//更新平衡因子parent->_bf = 0;subR->_bf = 0;}
右单旋
- 先让 subL 的右子树(subLR)作为 parent 的左子树。
- 然后让 parent 作为 subL 的右子树。
- 接下来让 subL 作为整个子树的根。
- 最后更新平衡因子。
void rotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;//滑动parent->_left = subLR;if (subLR) {subLR->_parent = parent;}//更新左子树的右子树subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else {subL->_parent == pparent;if (parent = pparent->_left) {pparent->_left = subL;}else{pparent->_right = subL;}}parent->_bf = 0;subL->_bf = 0;}
左右双旋
(1)以30为节点进行左单旋
(2)以90为节点进行右单旋
左右双旋后,平衡因子的更新随着 subLR 原始平衡因子的不同分为以下三种情况:
(1)当 subLR 原始平衡因子是 -1 时,左右双旋后 parent、subL、subLR 的平衡因子分别更新为 1、0、0
(2)当 subLR 原始平衡因子是 1 时,左右双旋后 parent、subL、subLR 的平衡因子分别更新为 0、-1、0
(3)当 subLR 原始平衡因子是 0 时(说明 subLR 为新增结点),左右双旋后 parent、subL、subLR 的平衡因子分别更新为0、0、0
可以看到,经过左右双旋后,树的高度没有发生变化,所以左右双旋后无需继续往上更新平衡因子。
代码实现:
void rotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int bf = subLR->_bf;rotateL(subL);//parent并不为最小不平衡子树的根,所以要在旋转以后重新赋值rotateR(parent);//各点的bf值由调整前subLR的bf值决定if (_bf == 0) {parent->_bf = 0;subL->_bf = 0;subLR->_bf = 0;}else if (bf == 1){parent->_bf = 0;subL->_bf = -1;subLR->_bf = 0;}else if (bf == -1){parent->_bf = 1;subL->_bf = 0;subLR->_bf = 0;}else{assert(false);}}
右左双旋
(1)以 90 为旋转点进行右单旋
(2)以 30 为旋转点进行左单旋
右左双旋后,平衡因子的更新随着 subLR 原始平衡因子的不同分为以下三种情况:、
(1)当 subRL 原始平衡因子是 1 时,左右双旋后 parent、subR、subRL 的平衡因子分别更新为 -1、0、0
(2)当 subRL 原始平衡因子是 -1 时,左右双旋后 parent、subR、subRL 的平衡因子分别更新为 0、1、0
(3)当 subRL 原始平衡因子是 0 时(说明 subRL为新增结点),左右双旋后 parent、subR、subRL 的平衡因子分别更新为0、0、0
可以看到,经过右左双旋后,树的高度没有发生变化,所以右左双旋后无需继续往上更新平衡因子。
代码实现:
void rotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int bf = subRL->_bf;rotateR(subR);rotateL(parent);if (_bf == 0) {parent->_bf = 0;subR->_bf = 0;subRL->_bf = 0;}else if (bf == 1){parent->_bf = -1;subR->_bf = 0;subRL->_bf = 0;}else if (bf == -1){parent->_bf = 0;subR->_bf = 1;subRL->_bf = 0;}else{assert(false);}}
AVL树的删除
要进行结点的删除,首先需要在树中找到对应key值的结点,寻找待删除结点的方法和二叉搜索树相同
1.先找到待删除的结点。
2.若找到的待删除结点的左右子树均不为空,则需要使用替换法进行删除。
替换法删除指的是:让待删除结点左子树当中key值最大的结点,或待删除结点右子树当中值最小的结点代替待删除结点被删除(代码中以后者为例),然后再将待删除结点的key值以及value值都改为代替其被删除的结点的值即可。
也就是说,我们最终找到的实际被删除的结点的左右子树当中至少有一个为空树。
在找到实际需要被删除的结点后,我们先不进行实际的删除,而是先进行平衡因子的更新,不然后续更新平衡因子时特别麻烦(已经尝试过),而更新平衡因子时的规则与插入结点时的规则是相反的,更新规则如下:
1.删除的结点在parent的右边,parent的平衡因子− − 。
2.删除的结点在parent的左边,parent的平衡因子+ +。
并且每更新完一个结点的平衡因子后,都需要进行以下判断:
1.如果parent的平衡因子等于-1或者1,表明无需继续往上更新平衡因子了。
2.如果parent的平衡因子等于0,表明还需要继续往上更新平衡因子。
3.如果parent的平衡因子等于-2或者2,表明此时以parent结点为根结点的子树已经不平衡了,需要进行旋转处理。
在旋转处理时,分为以下6种情况
1.当parent的平衡因子为-2,parent的左孩子的平衡因子为-1时,进行右单旋。
2.当parent的平衡因子为-2,parent的左孩子的平衡因子为1时,进行左右双旋。
3.当parent的平衡因子为-2,parent的左孩子的平衡因子为0时,也进行右单旋,但此时平衡因子调整与之前有所不同,具体看代码。
4.当parent的平衡因子为2,parent的右孩子的平衡因子为-1时,进行右左双旋。
5.当parent的平衡因子为2,parent的右孩子的平衡因子为1时,进行左单旋。
6.当parent的平衡因子为2,parent的右孩子的平衡因子为0时,也进行左单旋,但此时平衡因子调整与之前有所不同,具体看代码。
代码实现:
bool Erase(const K& key){//用于遍历二叉树Node* cur = parent;Node* parent = nullptr;//用于标记实际删除的节点和父节点Node* deletePos = nullptr;Node* deleteparentPos = nullptr;//确定删除节点while (cur) {if (key < cur->_kv.first) {parent = cur;cur = cur->_left;}else if (key > cur->_kv.first) {parent = cur;cur = cur->_right;}else//遇到了待删除的节点{if (cur->_left == nullptr)//如果待删除节点的左子树为空{if (cur == _root)//如果待删除节点是根节点{_root = cur->_right;if (_root) {_root->_parent = nullptr;}delete cur;return true;}else{deleteparentPos = parent;deletePos = cur;break;//有祖先节点说明要更新平衡因子}}else if (cur->_right == nullptr)//如果待删除节点的右子树为空{if (cur == _root)//如果待删除节点是根节点{_root = cur->_left;if (_root) {_root->_parent = nullptr;}delete cur;return true;}else{deleteparentPos = parent;deletePos = cur;break;//有祖先节点说明要更新平衡因子}}else//如果待删除节点的左右子树都不为空,替换法删除{//替换法删除//寻找待删除节点右子树中key值最小的节点作为实际删除节点(转化为左子树为空的局面)Node* minparent = cur;Node* minright = cur->_right;while (minright->_left){minparent = minright;minright = minright->_left;}//将待删除节点的属性更新为右子树中key值最小节点的属性cur->_kv.first = minright->_kv.first;cur->_kv.second = minright->_kv.second;//标记右子树中key值最小的节点为删除节点deleteparentPos = minparent;deletePos = minright;break;}}}//如果deletePos没有更新,说明没有找到根节点if (deletePos == nullptr){return false;}//记录待删除节点及其父节点(实际删除时要用)cur->_kv.first = minright->_kv.first;Node* del = deletePos;Node* delparent = deleteparentPos;//更新平衡因子while (deletePos != _root)//可能要一直更新到根节点{//判断删除的是父亲的哪一边,然后更新平衡因子if (deletePos = deleteparentPos->_left) {deleteparentPos->_bf++;}else{deleteparentPos->_bf--;}//判断是否需要往上继续更新if (abs(deleteparentPos->_bf) == 1) {break;}else if (deleteparentPos->_bf == 0) //其他两种情况树的高度都会变化所以要继续往上跟新平衡因子{deletePos = deleteparentPos;deleteparentPos = deleteparentPos->_parent;}else if (abs(deleteparentPos->_bf) == 2)//需要进行旋转处理{//判断旋转情况if (deleteparentPos->_bf = -2){if (deleteparentPos->_left->_bf == -1) {Node* tmp = deleteparentPos->_left;//记录右转后新的根节点rotateR(deleteparentPos);deleteparentPos = tmp;//旋转后根节点更新}else if(deleteparentPos->_left->_bf == 1) {Node* tmp = deleteparentPos->_left->_right;//记录右转后新的根节点rotateLR(deleteparentPos);deleteparentPos = tmp;//旋转后根节点更新}else//deleteparentPos->_left->_bf = 0{Node* tmp = deleteparentPos->_left;rotateR(deleteparentPos);deleteparentPos = tmp;deleteparentPos->_bf = 1;deleteparentPos->_right->_bf = -1;break;}}else//deleteparentPos->_bf = 2{if (deleteparentPos->_right->_bf == 1){Node* tmp = deleteparentPos->_right->_left;rotateRL(deleteparentPos);deleteparentPos = tmp;}else if (deleteparentPos->_right->_bf == -1){Node* tmp = deleteparentPos->_right;rotateL(deleteparentPos);deleteparentPos = tmp;}else{Node* tmp = deleteparentPos->_right;rotateL(deleteparentPos);deleteparentPos = tmp;deleteparentPos->_bf = -1;deleteparentPos->_left->_bf = 1;break;}}}else {assert(false);}//delParentPos树的高度变化,会影响其父结点的平衡因子,需要继续往上更新平衡因子deletePos = deleteparentPos;deleteparentPos = deleteparentPos->_parent;}if (del->_left == nullptr) {if (del = delparent->_left) {delparent->_left = del->_right;if (del->_right) {del->_right->_parent = delparent;}}else {delparent->_right = del->_right;if (del->_right) {del->_right->_parent = delparent;}}}else{if (del == delparent->_left) {delparent->_left = del->_left;if (del->_left) {del->_left->_parent = delparent;}}else {delparent->_right = del->_left;if (del->_left) {del->_left->_parent = delparent;}}}delete del;return true;}
AVL树的查找
Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key) // key值大于该结点的值{cur = cur->_left; // 在该结点的右子树当中查找}else if (cur->_kv.first < key) // key值小于该结点的值{cur = cur->_right; // 在该结点的左子树当中查找}else // 当前节点的值等于key值{return cur; //返回该结点}}return nullptr; //查找失败}
AVL树的高度
采用了分治的思想
private:
void _Height(Node* root){if (!root) {return 0;}int left = _Height(root->_left);int right = _Height(root->_right);return left > right ? left + 1 : right + 1;}
public:
void Height(){_Height(_root);}
AVL树的判定
bool _IsBalancedTree(Node* root) {if (!root) {return true;}int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);int diff = rightHeight - leftHeight;if (abs(diff) > 2) {cout << "wrong" << endl;return false;}else if (diff != root->_bf) {return false;}return _IsBalancedTree(root->_left) && _IsBalancedTree(root->_right);}
AVL树的遍历
void _Inorder(Node* root){if (!root) {return;}_Inorder(root->_left);cout << root->_kv.first << " ";_Inorder(root->_right);}