目录
1、AVL的概念
2、平衡因子的调整概念
3、AVL树的插入
3.1 调整平衡因子代码实现
3.2 右旋操作
3.2 左旋操作
3.3 双旋-先右旋再左旋
3.4 双旋-先左旋再右旋
3.5 旋转操作的小结
4、AVL的验证与实现
结语
前言:
在C++中,AVL树是在二叉搜索树的基础上优化而来的,因为二叉搜索树有一个缺陷,即如果插入的数据接近有序或者有序,那么二叉搜索树就会变成一边倒的结构,也就是”单支树“,而”单支树“查找数据效率就会变成和线性数据结构一样的O(N),失去了二叉树本有的优势。 单支树示意图如下:
因此两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年推出了AVL树的概念,AVL树在每一次插入新节点的同时,会自动调整该树的高度,即不会让每个节点的左右子树高度的绝对值不超过1,因此哪怕面对有序数据的插入也不会让树变成”单支树“,可以使查找数据的效率始终保持在O(log N)。
1、AVL的概念
AVL树满足以下两个条件:
1、其每颗子树都是AVL树。
2、每个节点的左右子树高度(可在节点中新加一个变量-平衡因子表示该节点的左右子树高度)的绝对值不超过1。
AVL树示意图如下:
其中,-1表示该节点的左子树高度高于右子树高度1个单位,0表示该节点左右子树高度一样,1表示该节点的右子树高度高于左子树高度1个单位。转换成公式:平衡因子=右子树高度-左子树高度。
2、平衡因子的调整概念
AVL树插入新节点的规则依旧是按照二叉搜索树的规则,即左节点小于根结点,而右节点大于跟节点。只不过AVL新加入了平衡因子的概念,所以每次插入节点后,都需要对平衡因子做出调整,比如插入的节点为该子树的根结点的右边,则根结点的平衡因子需要+1。
此时,平衡因子的状态就会出现三种情况:
1、插入节点后,根结点的平衡因子从正负1变为0,这种情况说明插入该节点后这棵树子树得到了平衡,无需做任何处理。
2、插入节点后,根结点的平衡因子从0变为1或-1,这种情况说明插入的节点破坏了该树的原有平衡,虽然当前子树的根结点的平衡因子的绝对值没有超过1,但是该子树的祖先节点的平衡因子的绝对值可能已经超过1了,所以需要”向上更新“,更改祖先节点的平衡因子。
3、插入节点后,该树的某个节点的平衡因子的绝对值超过了1,则需要对该子树进行旋转处理。
具体示意图如下:
此时会发现,不管新插入的节点在9的右边还是左边,都会导致节点8的平衡因子变成2,因为对于9来说可能在哪边插入节点都行,但是对于节点8来说,这两个插入的节点都在8的右子树,所以会导致8的平衡因子变成2。
在节点9的左边插入节点的情况如下:
只有在8的左边插入节点,才不会引发旋转调整,示意图如下:
3、AVL树的插入
AVL树的插入函数的实现可以分成三步:1、找到插入节点的合适位置。2、调整节点的平衡因子。3、对平衡因子异常的节点进行旋转操作。
第一步是复用了二叉搜索树的插入逻辑,即判断要插入节点的值是否大于或小于根结点,若大于根结点则往右子树遍历,若小于根结点则往左子树遍历,直到找到节点指向的nullptr处,然后将插入节点与其父母节点相连接。
第二步调整平衡因子逻辑即上文叙述。
第三步进行的旋转操作有:左旋、右旋、双旋转(即左右旋的结合),具体如下文。
3.1 调整平衡因子代码实现
将上述的调整场景转换为代码:
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;template<class K, class V>
class AVLTreeNode//节点
{AVLTreeNode<K, V>* _left;//指向左节点AVLTreeNode<K, V>* _right;//指向右节点AVLTreeNode<K, V>* _parent;//指向自己的父母节点pair<K, V> _kv;// 记录数据用的pair类型int _bf; // 平衡因子AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};template<class K, class V>
class AVLTree//AVL树
{typedef AVLTreeNode<K, V> Node;
public:bool 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;//AVL和二叉搜索树一样不允许有相同数据}}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->_right){parent->_bf++;//插入的位置是节点的右边,则平衡因子++}else{parent->_bf--;//插入的位置是节点的左边,则平衡因子--}if (parent->_bf == 1 || parent->_bf == -1){// 继续更新parent = parent->_parent;cur = cur->_parent;}else if (parent->_bf == 0)//等于0说明平衡了,则不处理{break;}else if (parent->_bf == 2 || parent->_bf == -2){// 需要旋转处理 -- 1、让这颗子树平衡 2、降低这颗子树的高度}else{assert(false);}}return true;}private:Node* _root = nullptr;
};
以上代码已经实现了合适位置的查找和平衡因子的调整,插入函数的实现还差最后一步,即平衡因子等于2或者-2时旋转操作的实现。
3.2 右旋操作
右旋操作是针对平衡因子为-2的场景,说明该节点的左子树比右子树要高,需要右旋降低左子树的高度。并且旋转完成后要更新平衡因子,具体操作示意图如下:
从结果可以看到,原本平衡因子是-2的节点经过旋转操作之后变成了0。并且旋转之后的节点与节点之间的逻辑依然是遵循小于根结点的在左边,大于根节点的在右边。
3.2 左旋操作
左旋操作是针对平衡因子为2的场景,说明该节点的右子树比左子树要高,需要左旋降低右子树的高度。并且旋转完成后要更新平衡因子,具体操作示意图如下:
从结果可以看到,原本平衡因子是2的节点经过旋转操作之后变成了0。
无论是左旋还是右旋,都要注意两个事项:
一、拿上述左旋举例,节点12不一定存在左孩子,如果不存在左孩子则10的平衡因子是-1。
二、节点10不一定是根结点,可能是某个节点的左子树或者右子树,若10只是子树,则旋转后要将节点12与原先节点10的父母节点相连接。
3.3 双旋-先右旋再左旋
从上述的例子可以发现,插入的节点都处于”边缘“节点下,比如拿上述的左旋举例,若插入的节点是插入到节点11下则如何调整呢?这时候就要用到双选调整,比如上述的节点14作为节点11的孩子插入,则需要以节点12为一棵子树,先进行右旋操作,然后再以根结点10进行左旋操作。
先右旋再左旋示意图如下:
3.4 双旋-先左旋再右旋
用上述右旋的例子来进行说明,如果插入的节点是作为节点8的孩子节点,则需要先以节点6为子树进行左旋,然后再以根结点10进行整棵树的右旋。
先左旋再右旋的示意图如下:
3.5 旋转操作的小结
经过上述旋转操作的细述,大致可以得出以下结论:
一、平衡因子为2的节点,说明该节点的右子树高,因此只需要关注该节点的右孩子,其右孩子的平衡因子会有两种情况:
1、该节点平衡因子若为1则只需要进行左旋转即可。
2、该节点平衡因子若为-1则需要进行双旋-先右旋再左旋。
二、平衡因子为-2的节点,说明该节点的左子树高,因此只需要关注该节点的左孩子,其左孩子的平衡因子会有两种情况:
1、该节点平衡因子若为1则需要进行双旋-先左旋再右旋。
2、该节点平衡因子若为-1则只需要进行右旋转即可。
从以上结论可以写出旋转代码。
4、AVL的验证与实现
判断一棵树是否为AVL树的依据有两点:1、中序遍历的方式打印出来的是有序数据。2、每个节点的平衡因子的绝对值不超过2。
AVL树的实现代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <assert.h>
#include <time.h>
#include<iostream>
using namespace std;template<class K, class V>
struct AVLTreeNode
{AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;pair<K, V> _kv;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool 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->_left = cur;}else{parent->_right = cur;}cur->_parent = parent;// 更新平衡因子while (parent){if (cur == parent->_right){parent->_bf++;}else{parent->_bf--;}if (parent->_bf == 1 || parent->_bf == -1){parent = parent->_parent;cur = cur->_parent;}else if (parent->_bf == 0){break;}else if (parent->_bf == 2 || parent->_bf == -2){// 旋转处理:1、让这颗子树平衡 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);}else{assert(false);}break;}else{assert(false);}}return true;}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){return _IsBalance(_root);}int Height(){return _Height(_root);}private:int _Height(Node* root){if (root == NULL)return 0;int leftH = _Height(root->_left);int rightH = _Height(root->_right);return leftH > rightH ? leftH + 1 : rightH + 1;}bool _IsBalance(Node* root)//观察平衡因子是否有问题{if (root == NULL){return true;}int leftH = _Height(root->_left);int rightH = _Height(root->_right);if (rightH - leftH != root->_bf){cout << root->_kv.first << "平衡因子异常" << endl;return false;}return abs(leftH - rightH) < 2&& _IsBalance(root->_left)&& _IsBalance(root->_right);}void RotateL(Node* parent)//左旋{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;Node* ppnode = parent->_parent;subR->_left = parent;parent->_parent = subR;if (ppnode == nullptr){_root = subR;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}parent->_bf = subR->_bf = 0;}void RotateR(Node* parent)//右旋{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* ppnode = parent->_parent;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}subL->_bf = parent->_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 == 1){parent->_bf = 0;subLR->_bf = 0;subL->_bf = -1;}else if (bf == -1){parent->_bf = 1;subLR->_bf = 0;subL->_bf = 0;}else if (bf == 0){parent->_bf = 0;subLR->_bf = 0;subL->_bf = 0;}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 == 1){subR->_bf = 0;parent->_bf = -1;subRL->_bf = 0;}else if (bf == -1){subR->_bf = 1;parent->_bf = 0;subRL->_bf = 0;}else if (bf == 0){subR->_bf = 0;parent->_bf = 0;subRL->_bf = 0;}else{assert(false);}}void _InOrder(Node* root)//中序遍历AVL树{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}
private:Node* _root = nullptr;
};int main()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };AVLTree<int, int> t1;for (auto e : a){t1.Insert(make_pair(e, e));}t1.InOrder();cout << t1.IsBalance() << endl;return 0;
}
结语
以上就是关于AVL的讲解,AVL的重点在于对旋转的理解,理清旋转的逻辑与各个节点之间的逻辑关系尤为重要。最后希望本文可以给你带来更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!