赞
踩
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下
平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
某结点的右子树与左子树的高度(深度)差即为该结点的平衡因子。平衡二叉树上所有结点的平衡因子只可能是 -1,0 或 1
一个AVL树的结点包含了5个成员,一个是当前节点数据_data
,一个是左孩子结点_left
,一个是右孩子结点_right
,一个是父节点_parent
,一个是平衡因子_bf
;
template<class T> struct AVLNode { AVLNode<T>* _parent; AVLNode<T>* _left; AVLNode<T>* _right; T _val; int _bf;//平衡因子 AVLNode(const T& val = T()) :_parent(nullptr) ,_left(nullptr) ,_right(nullptr) ,_val(val) ,_bf(0)//初始化为0,叶子无左右孩子 {} }; //AVL树 template<class T> class AVLTreeNode { public: typedef AVLNode<T> Node; private: Node* _root; };
第三步要分很多种情况,例如我们拿下图来举例说明
1、假如我们在结点23的左边插入结点20此时此数还是一棵平衡的二叉树,需要修改其父路径上的平衡因子
2、假如我们在结点4的左边插入一个节点3,此时此树还是一棵平衡的树,只需要修改其父节点的平衡因子,因为此结点的插入后,往上追溯的平衡因子修改后为0,并不会对平衡因子为0的父节点的平衡因子造成影响。
3、假如我们在上一幅图中再插入一个节点18。则此时已经不是一棵AVL树了,因为结点23的平衡因子不在[-1, 1]的范围之内。此时需要进行旋转调整
调整前:
调整后
但这种调整只是其中的一种,我们下面看看调整以及如何调整
当我们的子树如下图结构时,并且在a处插入一个结点而导致的不平衡,也就是E结点平衡因子小于-1。此时就需要在结点E进行右单旋。
左边的左边高,需要降低左子树的高度,提高右子树的高度。调整后结点S和结点E的平衡因子都为0,其他因子不受影响。
动画演示:中间的一条根为S结点的父节点
需要修改的连接有6条连接,4个结点
右旋核心代码如下:
void RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if (subLR)//如果不为空需要更新_parent subLR->_parent = parent; if (parent == _root) //父节点为根,则不考虑pparent的连接 { //更新根节点 _root = subL; subL->_parent = nullptr; } else { Node* pparent = parent->_parent; if (pparent->_left == parent) pparent->_left = subL; else pparent->_right = subL; subL->_parent = pparent; } subL->_right = parent; parent->_parent = subL;//必须为最后一个连接 subL->_bf = parent->_bf = 0; }
测试:
我们先给出insert的部分代码
bool insert(const T& val) { if (_root == nullptr) { _root = new Node(val); return true; } //1.查找到要插入的位置 Node* cur = _root; Node* parent = nullptr; while (cur) { parent = cur;//记录插入节点的父节点 if (cur->_val == val)//存在相同节点退出 return false; else if (cur->_val > val)//插入节点比当前节点小,则硬插入当前节点的左子树中 cur = cur->_left; else cur = cur->_right; } //2.连接节点关系 cur = new Node(val); if (parent->_val > val) parent->_left = cur; else parent->_right = cur; cur->_parent = parent; //3.修改平衡因子,调整成AVL树 while (parent) { //更新父节点的平衡因子,判断是否还平衡 if (parent->_left == cur) --parent->_bf; else ++parent->_bf; if (parent->_bf == 0) //表示parent的子树比较短的子树高度+1,不影响parent的父节点的平衡因子,插入完毕 break; else if (parent->_bf == 1 || parent->_bf == -1)//表示parent更新前为0,更新后不为0,树的高度有变,会影响parent的父节点 { //向上追溯修改parent父节点的平衡因子 cur = parent; parent = parent->_parent; } else if (abs(parent->_bf) == 2) { if (parent->_bf == -2 && cur->_bf == -1)//左边的左边高 { //右单旋 RotateR(parent); } //... break; } } return true; }
测试代码:
void test() { AVLTreeNode<int> avl; avl.insert(5); avl.insert(3); // 5 // 3 avl.insert(1); //右单旋 // 3 // 1 5 avl.insert(0); avl.insert(2); avl.insert(-1);//右单旋 //右旋前 // 3 // 1 5 // 0 2 //-1 //右旋后 // 1 // 0 3 // -1 2 5 }
运行结果:
执行完insert(1)后
执行完insert(-1)后,以1位根的左右子树
当在右子树的右节点中插入一个节点而导致树的平衡因子大于2。此时就需要进行左单旋。应用场景如下
右边的右边高,需要降低右边的高度,从而提高左边的高度。调整后结点S和结点E的平衡因子都为0,其他因子不受影响。
动画演示:中间的一条根为S结点的父节点
连接方式和右单旋非常类似,只是连接的结点不同
左旋代码:
void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; parent->_right = subRL; if (subRL) subRL->_parent = parent; if (parent == _root) { _root = subR; subR->_parent = bullptr; } else { Node* pparent = parent->_parent; if (pparent->_left == parent) pparent->_left = subR; else pparent->_right = subR; subR->_parent = pparent; } subR->_left = parent; parent->_parent = subR; subR->_bf = parent->_bf = 0; }
测试:
void test() { AVLTreeNode<int> avl; avl.insert(5); avl.insert(3); avl.insert(1); //右单旋 avl.insert(0); avl.insert(2); avl.insert(-1);//右单旋 // 1 // 0 3 // -1 2 5 avl.insert(10); avl.insert(15); //左单旋 //左单旋前 // 1 // 0 3 // -1 2 5 // 10 // 15 //左单旋后 // 1 // 0 3 // -1 2 10 // 5 15 avl.insert(20);//左单旋 //左单旋前 // 1 // 0 3 // -1 2 10 // 5 15 // 20 //左单旋后 // 3 // 1 10 // 0 2 5 15 //-1 20 }
走到第一个左单旋后进行旋转后左右子树情况
走到第二个左单旋后进行旋转后左右子树情况
左边的右边高,此时需要进行两次旋转,先在C结点进行左旋,再在P结点进行右旋
我们在左旋和右旋旋转完后他们他们的结点的平衡因子都设置为0,可是我们发现p结点旋转完后的平衡因子不是0,所以我们在旋转后完应该要分情况处理。我们再来看看在c子树下插入结点后他们的平衡因子如何
我们发现P结点的平衡因子为0,而C结点的平衡因子为-1。总结下来就是谁拿到高的那边,谁的平衡因子就为0。而没拿到的另一边的高度应该减1
代码:
else if (parent->_bf == -2 && cur->_bf == 1) //左边的右边高 { //左右双旋 Node* subLR = cur->_right; int bf = subLR->_bf; RotateL(cur); RotateR(parent); if (bf == -1) { parent->_bf = 1; cur->_bf = 0; } if (bf == 1) { parent->_bf = 0; cur->_bf = -1; } }
右边的左边高,此时需要进行两次旋转,先在C结点进行右旋,再在P结点进行左旋
和左右双旋类似,CL结点的平衡因子不同,导致最后P结点和C结点的平衡因子也不同
当在CL结点的右边插入时,C结点的平衡因子为0,P结点的平衡因子为-1;当在CL结点的左边插入结点时,C的结点的平衡因子为1,P结点的平衡因子为0
代码:
else if(parent->_bf == 2 && cur->_bf == -1) //右边的左边高 { //右左双旋 Node* subRL = subR->_left; int bf = subRL->_bf; RotateR(cur); RotateL(parent); if (bf == 1) { parent->_bf = -1; cur->_bf = 0; } if (bf == -1) { parent->_bf = 0; cur->_bf = 1; } }
代码我们都写好了,现在我们就通过验证AVL树来判断我们写的是否正确
当以parent为根的子树不平衡,也就是parent的平衡因子为2或者-2时,会根据不同的平衡原因来进行不同的旋转操作
判断AVL树的两个条件
验证第一个条件可以使用中序遍历来判断,当输出的结果是有序时表示该树为二叉搜索树。我们通过随机值的插入与遍历,来验证该树是否是有序输出
void inorder()
{
_inorder(_root);
cout << endl;
}
void _inorder(Node* root)
{
if (root)
{
_inorder(root->_left);
cout << root->_val << " ";
_inorder(root->_right);
}
}
运行结果:升序输出,该树为二叉搜索树
我们知道一棵树的平衡因子为右子树的高度减去左子树的高度,我们去递归获取子树的高度再利用右左子树高度差比较,相等就继续向下判断,一旦不相等或者平衡因子有误则表示不是AVL树
bool isBalance() { cout << endl; return _isBalance(_root); } int height(Node* root) { if (root == nullptr) return 0; int left = height(root->_left); int right = height(root->_right); return left > right ? left + 1 : right + 1; } bool _isBalance(Node* root) { if (root == nullptr) return true; //查看平衡因子是否和左右子树的高度差一致 int left = height(root->_left); int right = height(root->_right); if (right - left != root->_bf || abs(root->_bf) > 1)//不相等或者平衡因子有误则表示不是AVL树 return false; return _isBalance(root->_left) && _isBalance(root->_right); }
运行结果:该树是一棵AVL树
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。