侯马建设规划局网站,青果软件学院教务网络管理系统,开公司流程及费用,下载优化大师app目录 一、AVL树的概念二、AVL树的实现1、AVL树的定义2. 平衡二叉树的插入2.1 按照二叉排序树的方式插入并更新平衡因子2.2 AVL树的旋转2.2.1 新节点插入较高左子树的左侧#xff08;LL平衡旋转#xff09;2.2.2 新节点插入较高右子树的右侧#xff08;RR平衡旋转#xff09… 目录 一、AVL树的概念二、AVL树的实现1、AVL树的定义2. 平衡二叉树的插入2.1 按照二叉排序树的方式插入并更新平衡因子2.2 AVL树的旋转2.2.1 新节点插入较高左子树的左侧LL平衡旋转2.2.2 新节点插入较高右子树的右侧RR平衡旋转2.2.3 新节点插入较高左子树的右侧LR平衡旋转2.2.4 新节点插入较高右子树的左侧RL平衡旋转2.2.5 总结 3 平衡二叉树的删除了解即可4 平衡二叉树的验证 三、平衡二叉树的效率分析 一、AVL树的概念
二叉排序树虽可以缩短查找的效率但如果数据有序或接近有序二叉搜索树将退化为单支树查找元素相当于在顺序表中搜索元素效率低下。 为了避免树的高度增长过快降低二叉排序树的性能规定在插入和删除结点时要保证任意结点的左、右子树高度差的绝对值不超过1将这样的二叉树称为平衡二叉树也称AVL树。
一棵AVL树或者是空树或者是具有以下性质的二叉搜索树
它的左右子树都是AVL树左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
二、AVL树的实现
1、AVL树的定义
AVL树结点的定义
templateclass K, class V
struct AVLTreeNode
{AVLTreeNode(const pairK, V kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}AVLTreeNodeK, V* _left;AVLTreeNodeK, V* _right;AVLTreeNodeK, V* _parent; // 使用三叉链方便后续处理但要记得维护pairK, V _kv; // 保存键值对int _bf; // 平衡因子
};2. 平衡二叉树的插入
2.1 按照二叉排序树的方式插入并更新平衡因子
AVL树就是在二叉排序树的基础上加上了平衡因子因此AVL树也可以看成是二叉排序树。那么AVL树的插入过程可以分为两步 (1) 按照二叉排序树的方法插入新结点 (2) 调整结点的平衡因子
bool Insert(const pairK, V kv)
{// 先按照二叉排序树的方法进行结点插入if (_root nullptr){_root new Node(kv);return true;}Node* parent nullptr;Node* cur _root;while(cur){if (kv.first cur-_kv.first){parent cur;cur cur-_left;}else if (kv.first cur-_kv.first){parent cur;cur cur-_right;}else{return false;}}cur new Node(kv);if (kv.first parent-_kv.first){parent-_left cur;}else{parent-_right cur;}cur-_parent parent;// 新结点插入后AVL树的平衡性可能会遭到破坏此时就需要更新平衡因子并检测是否// 破坏了AVL树的平衡性while (parent){/*cur插入后parent的平衡因子一定需要调整在插入之前parent的平衡因子分为三种情况-10, 1, 分以下两种情况1. 如果cur插入到parent的左侧只需给parent的平衡因子-1即可2. 如果cur插入到parent的右侧只需给parent的平衡因子1即可*/if (parent-_left cur){--parent-_bf;}else{parent-_bf;}/*此时parent的平衡因子可能有三种情况0正负1 正负21. 如果parent的平衡因子为0说明插入之前parent的平衡因子为正负1插入后被调整成0此时满足AVL树的性质插入成功2. 如果parent的平衡因子为正负1说明插入前parent的平衡因子一定为0插入后被更新成正负1此时以parent为根的树的高度增加需要继续向上更新3. 如果parent的平衡因子为正负2则parent的平衡因子违反平衡树的性质需要对其进行旋转处理*/if (0 parent-_bf){break;}else if (1 parent-_bf || -1 parent-_bf){cur cur-_parent;parent parent-_parent;}else if (2 parent-_bf || -2 parent-_bf){// 旋转处理}else{// 如果平衡因子不是以上几种情况说明代码逻辑错误assert(false);}}return true;
}2.2 AVL树的旋转
如果在一棵原本是平衡的AVL树中插入一个新节点可能造成不平衡此时必须调整树的结构使之平衡化。根据节点插入位置的不同AVL树的旋转分为四种LL平衡旋转右旋RR平衡旋转左旋LR平衡旋转先左旋后右旋RL平衡旋转先右旋后左旋
2.2.1 新节点插入较高左子树的左侧LL平衡旋转 上图在插入前AVL树是平衡的新节点插入到30的左子树(注意此处不是左孩子)中30左子树增加了一层导致以60为根的二叉树不平衡要让60平衡只能将60左子树的高度减少一层右子树增加一层即将左子树往上提这样60转下来因为60比30大只能将其放在30的右子树而如果30有右子树右子树根的值一定大于30小于60只能将其放在60的左子树旋转完成后更新节点的平衡因子即可。
在旋转过程中有以下几种情况需要考虑
30节点的右孩子可能存在也可能不存在60可能是根节点也可能是子树 如果是根节点旋转完成后要更新根节点 如果是子树可能是某个节点的左子树也可能是右子树
void RotateR(Node* parent)
{// subLparent的左孩子// subLRparent的左孩子的右孩子注意该点可能不存在Node* subL parent-_left;Node* subLR subL-_right;subL-_right parent;parent-_left subLR;Node* ppnode parent-_parent; // 记录parent的父结点用于连接新的子树parent-_parent subL;if (subLR){subLR-_parent parent;}if (ppnode nullptr){_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;
}2.2.2 新节点插入较高右子树的右侧RR平衡旋转 具体实现参考右旋即可。
void RotateL(Node* parent)
{Node* subR parent-_right;Node* subRL subR-_left;subR-_left parent;parent-_right subRL;Node* ppnode parent-_parent; // 记录parent的父结点parent-_parent subR;if (subRL){subRL-_parent parent;}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;
}2.2.3 新节点插入较高左子树的右侧LR平衡旋转 将双旋变成单旋后再旋转即先对30进行左单旋然后再对90进行右单旋旋转完成后再考虑平衡因子的更新。
void RotateLR(Node* parent)
{// subLparent的左孩子// subLRparent的左孩子的右孩子注意该点可能不存在Node* subL parent-_left;Node* subLR subL-_right;// 旋转之前保存pSubLR的平衡因子旋转完成之后需要根据该平衡因子来调整其他节点的平衡因子int bf subLR-_bf;RotateL(parent-_left);RotateR(parent);if (1 bf){subL-_bf -1;}else if (-1 bf){parent-_bf 1;}
}2.2.4 新节点插入较高右子树的左侧RL平衡旋转 参考右左双旋。
void RotateRL(Node* parent)
{Node* subR parent-_right;Node* subRL subR-_left;int bf subRL-_bf;RotateR(parent-_right);RotateL(parent);if (1 bf){parent-_bf -1;}else if (-1 bf){subR-_bf 1;}
}2.2.5 总结
假如以parent为根的子树不平衡即parent的平衡因子为2或者-2分以下情况考虑
parent的平衡因子为2说明parent的右子树高设parent的右子树的根为subR 当subR的平衡因子为1时执行左单旋 当subR的平衡因子为-1时执行右左双旋parent的平衡因子为-2说明parent的左子树高设parent的左子树的根为subL 当subL的平衡因子为-1是执行右单旋 当subL的平衡因子为1时执行左右双旋
旋转完成后原parent为根的子树个高度降低已经平衡不需要再向上更新。
3 平衡二叉树的删除了解即可
因为AVL树也是二叉排序树可按照二叉排序树的方式将节点删除然后再更新平衡因子只不错与删除不同的时删除节点后的平衡因子更新最差情况下一直要调整到根节点的位置。 平衡二叉树删除操作的具体步骤
先按照二叉排序树的方式删除结点一路向上找到最小不平衡子树找不到就结束找最小不平衡子树下最高的儿子和孙子根据孙子的位置调整平衡 孙子在LL右单旋孙子在RR左单旋孙子在LR先左旋再右旋孙子再RL先右旋再左旋 如果不平衡向上传导继续第二步 对最小不平衡子树的旋转可能导致树变矮从而导致上层祖先不平衡
4 平衡二叉树的验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制因此要验证AVL树可以分两步
验证其为二叉搜索树 如果中序遍历可得到一个有序的序列就说明为二叉搜索树验证其为平衡树 每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)节点的平衡因子是否计算正确
// 求二叉树的高度
int _Height(Node* root)
{if (root nullptr){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 nullptr){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 rightH - leftH 2 _Isbalance(root-_left) _Isbalance(root-_right);
}三、平衡二叉树的效率分析
在平衡二叉树上进行查找的过程与二叉排序树相同。因此在查找过程中进行关键字的比较次数不超过树的深度。假设以 n h n_h nh表示深度为h的平衡二叉树中含有的最少结点数。 n 0 0 , n 1 1 , n 2 2 n_00,n_11,n_22 n00,n11,n22并且有 n h n h − 2 n h − 1 1 n_hn_{h-2}n_{h-1}1 nhnh−2nh−11含有n个结点的平衡二叉树的最大深度为 O ( l o g 2 n ) O(log_2n) O(log2n)因此平均查找效率为 O ( l o g 2 n ) O(log_2n) O(log2n)。 但是如果要对AVL树做一些结构修改的操作性能非常低下比如插入时要维护其绝对平衡旋转的次数比较多更差的是在删除时有可能一直要让旋转持续到根的位置。因此如果需要一种查询高效且有序的数据结构而且数据的个数为静态的(即不会改变)可以考虑AVL树但一个结构经常修改就不太适合。