淘宝网站建设类别,长春净月潭建设投资集团网站,二级目录做网站,北京做企业网站的公司一、 AVL树的概念
map、multimap、set、multiset 在其文档介绍中可以发现#xff0c;这几个容器有个共同点是#xff1a;其底层都是按照二叉搜索树来实现的#xff0c;但是二叉搜索树有其自身的缺陷#xff0c;假如往树中插入的元素有序或者接近有序#xff0c;二叉搜索树…一、 AVL树的概念
map、multimap、set、multiset 在其文档介绍中可以发现这几个容器有个共同点是其底层都是按照二叉搜索树来实现的但是二叉搜索树有其自身的缺陷假如往树中插入的元素有序或者接近有序二叉搜索树就会退化成单支树时间复杂度会退化成O(N)O(N)O(N)因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理即采用平衡树来实现。
二叉搜索树虽可以缩短查找的效率但如果数据有序或接近有序二叉搜索树将退化为单支树查找元素相当于在顺序表中搜索元素效率低下。
因此两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种解决上述问题的方法当向二叉搜索树中插入新结点后如果能保证每个结点的左右子树高度之差的绝对值不超过1需要对树中的结点进行调整即可降低树的高度从而减少平均搜索长度。
一棵AVL树或者是空树或者是具有以下性质的二叉搜索树
它的左右子树都是AVL树左右子树高度之差简称平衡因子balance factor的绝对值不超过1-1/0/1
如果一棵二叉搜索树是高度平衡的它就是AVL树。如果它有n个结点其高度可保持在O(log2n)O(log_2 n)O(log2n)搜索时间复杂度O(log2nlog_2 nlog2n)。 二、AVL树节点的定义
AVL树的节点是三叉链结构即parent、left和right它们分别指向当前节点的父节点、左子节点和右子节点。通过这种方式可以在O(1)O(1)O(1)的时间内找到一个节点的父节点、左子节点和右子节点。 namespace AVL
{templateclass K, class Vstruct AVLTreeNode {AVLTreeNodeK, V* _left;AVLTreeNodeK, V* _right;AVLTreeNodeK, V* _parent; //指向父节点的指针pairK, V _kv;int _bf; // 平衡因子AVLTreeNode(const pairK, V kv) :_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_bf(0){}};templateclass K, class Vclass AVLTree{typedef AVLTreeNodeK, V Node;public:private:Node* _root nullptr;};
}三、AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步
按照二叉搜索树的方式插入新节点调整节点的平衡因子
插入在左平衡因子-1插入在右平衡因子1
是否继续更新的依据parent所在子树的高度是否变化 parent-_bf 0说明之前parent-_bf是 1 或者 -1 说明之前parent一边高一边低这次插入填上矮的那边parent所在子树高度不变不需要继续往上更新 parent-_bf 1或-1说明之前是parent-_bf 0两边一样高现在插入一边更高了parent所在子树高度变了继续往上更新 parent-_bf 2或-2说明之前parent-_bf 1或者-1现在插入严重不平衡违反规则就地处理–旋转
bool insert(const pairK, V kv)
{// 1. 先按照二叉搜索树的规则将节点插入到AVL树中// 空树直接构建根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-_right;}else if (kv.first cur-_kv.first) // 小了往左边走{parent cur;cur cur-_left;}else{return false;// 相等不插入}}//开始插入cur new Node(kv);// 新插入的节点// 小的插入左大的插入右if (kv.first parent-_kv.first){parent-_left cur;cur-_parent parent;// 三叉链不要忘记更新父指针}else{parent-_right cur;cur-_parent parent;}// 2. 新节点插入后AVL树的平衡性可能会遭到破坏// 此时需要更新平衡因子并检测是否破坏了AVL树的平衡性while (parent) // parent为空也就更新到根停止{// 更新平衡因子// 新增在左parent-bf--;// 新增在右parent-bf;if (cur parent-_left){parent-_bf--;}else{parent-_bf;}//检测平衡因子if (parent-_bf 0){break;// 无需继续更新}else if (parent-_bf 1 || parent-_bf -1){// 插入前parent的平衡因子是0插入后parent的平衡因为为1 或者 - 1 说明以parent为根的二叉树// 的高度增加了一层因此需要继续向上调整cur parent;parent parent-_parent;}else if (parent-_bf 2 || parent-_bf -2){// parent的平衡因子为-2/2违反了AVL树的平衡性// 需要对以 parent 为根的树进行 旋转 处理// 旋转break; // 旋转完成后原 parent 为根的子树高度降低已经平衡不需要再向上更新}else{assert(false); // 平衡因子异常绝对值大于2}}return true;
}四、AVL树的旋转
在一棵原本是平衡的AVL树中插入一个新节点可能造成不平衡此时可通过旋转调整树的结构使之平衡化。
旋转的目的:
让这颗子树左右高度不超过1旋转过程中继续保持是搜索树更新调整孩子节点的平衡因子让这颗子树的高度跟插入前保持一致
根据节点插入位置的不同AVL树的旋转分为四种
新节点插入 较高 左左左子树的左左左侧—左左右单旋 在插入前图中AVL树是平衡的新节点插入到30的左子树注意此处不是左孩子图中a/b/c是高度为 h 的AVL子树中30左子树增加了一层导致以60为根的二叉树不平衡
要让60平衡只能将60左子树的高度减少一层右子树增加一层即将左子树往上提这样60转下来因为60比30大只能将其放在30的右子树而如果30有右子树右子树根的值一定大于30小于60只能将其放在60的左子树旋转完成后更新节点的平衡因子即可。
在旋转过程中有以下几种情况需要考虑
30节点的右孩子可能存在也可能不存在60可能是根节点也可能是子树如果是根节点旋转完成后要更新根节点如果是子树可能是某个节点的左子树也可能是右子树
这里举一些详细的例子进行画图考虑各种情况加深旋转的理解
h 0则a/b/c是空树 h 1 h 2的情况已经有很多种了随着h的增加情况会越来越复杂 看图写代码 void RotateR(Node* parent)
{Node* subL parent-_left;Node* subLR subL-_right;// 30的右变成60的左parent-_left subLR;if (subLR ! nullptr) // 30的右不为空更新_parent指针{subLR-_parent parent;}Node* ppNode parent-_parent;// 60变成30的右parent subL-_right;parent-_parent subL;//不要忘记更新parent的父指针if (_root parent) // parent就是根//if (ppNode nullptr) //也可以使用这个判断条件{_root subL;_root-_parent nullptr;}else // parent是左或右子树{// parent是左就把subL链接到左是右就链接到右if (parent ppNode-_left){ppNode-_left subL;}else{ppNode-_right subL;}subL-_parent ppNode;// 同样不要忘记更新subL的父指针}// 最后更新parent和subL的平衡因子parent-_bf subL-_bf 0;
}
新节点插入较高右右右子树的右右右侧—右右左单旋 左单旋实现及情况考虑可参考右单旋
h 0的情况 h 1 void RotateL(Node* parent)
{Node* subR parent-_right;Node* subRL subR-_left;// 60的左变成30的右parent-_right subRL;// 更新subRL的父指针if (subRL){subRL-_parent parent;}Node* ppNode parent-_parent;// 30变成60的左subR-_left parent;parent-_parent subR;//if (_root parent)if (ppNode nullptr){_root subR;_root-_parent nullptr;}else{if (parent ppNode-_left){ppNode-_left subR;}else{ppNode-_right subR;}subR-_parent ppNode;}parent-_bf subR-_bf 0;
}像下图的情况简单的单旋已经不能正确调整平衡需要使用双旋不同轴点的单旋 新节点插入较高左左左子树的右右右侧—左右先左单旋再右单旋
a/d是高度为 h 的AVL树 b/c是高度为 h - 1 的AVL树
h 0 h 1 看图写代码
void RotateLR(Node* parent)
{Node* subL parent-_left;Node* subLR subL-_right;int bf subLR-_bf;// 对30左单旋对90右单旋RotateL(parent-_left);RotateR(parent);// 最后更新平衡因子if (bf 0) // subLR自己是新增{parent-_bf 0;subL-_bf 0;subLR-_bf 0;}else if (bf -1) // 在subLR的左新增{parent-_bf 1;subL-_bf 0;subLR-_bf 0;}else if (bf 1) // 在subLR的右新增{parent-_bf 0;subL-_bf -1;subLR-_bf 0;}else{assert(false);// 异常处理}
}新节点插入较高右右右子树的左左左侧—右左先右单旋再左单旋 h 0 h 1 void RotateRL(Node* parent)
{Node* subR parent-_right;Node* subRL subR-_left;int bf subRL-_bf;RotateR(parent-_right);RotateL(parent);if (bf 0){parent-_bf 0;subR-_bf 0;subRL-_bf 0;}else if (bf -1){parent-_bf 0;subR-_bf 1;subRL-_bf 0;}else if (bf 1){parent-_bf -1;subR-_bf 0;subRL-_bf 0;}else{assert(false);}
}总结 假如以 parent 为根的子树不平衡即 parent 的平衡因子为 2 或者 -2 分以下情况考虑:
parent 的平衡因子为 2说明 parent 的右子树高设 parent 的右子树的根为 subR 当 subR 的平衡因子为 1 时执行左单旋当 subR 的平衡因子为 -1 时执行右左双旋 parent 的平衡因子为 -2 说明 parent 的左子树高设 parent的左子树的根为 subL 当 subL 的平衡因子为 -1 是执行右单旋当 subL 的平衡因子为 1 时执行左右双旋
旋转完成后原 parent 为根的子树高度降低已经平衡不需要再向上更新。
insert 时平衡因子检测的整体代码
while (parent) // parent为空也就更新到根停止
{// 更新平衡因子// 新增在左parent-bf--;// 新增在右parent-bf;if (cur parent-_left){parent-_bf--;}else{parent-_bf;}//检测if (parent-_bf 0){break;// 无需继续更新}else if (parent-_bf 1 || parent-_bf -1){// 插入前parent的平衡因子是0插入后parent的平衡因为为1 或者 - 1 说明以parent为根的二叉树// 的高度增加了一层因此需要继续向上调整cur parent;parent parent-_parent;}else if (parent-_bf 2 || parent-_bf -2){// parent的平衡因子为-2/2违反了AVL树的平衡性// 需要对以 parent 为根的树进行 旋转 处理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; // 旋转完成后原 parent 为根的子树高度降低已经平衡不需要再向上更新}else{assert(false); // 平衡因子异常绝对值大于2}
}AVL树的整体代码AVL树的简单模拟实现 五、AVL树的验证
AVL树是在二叉搜索树的基础上加入了平衡性的限制因此要验证AVL树可以分两步
验证其为二叉搜索树 如果中序遍历可得到一个有序的序列就说明为二叉搜索树验证其为平衡树 每个节点子树高度差的绝对值不超过1注意节点中如果有平衡因子还需验证节点的平衡因子是否计算正确
int Height(Node* root)
{if (root nullptr)return 0;int lh Height(root-_left);int rh Height(root-_right);return lh rh ? lh 1 : rh 1;
}bool IsBalance()
{return IsBalance(_root);
}bool IsBalance(Node* root)
{if (root nullptr){return true;}int leftHeight Height(root-_left);int rightHeight Height(root-_right);if (rightHeight - leftHeight ! root-_bf){std::cout root-_kv.first 平衡因子异常 std::endl;return false;}return abs(rightHeight - leftHeight) 2 IsBalance(root-_left) IsBalance(root-_right);
}六、AVL树的性能
AVL树是一棵绝对平衡的二叉搜索树其要求每个节点的左右子树高度差的绝对值都不超过1这样可以保证查询时高效的时间复杂度即log2(N)log_2 (N)log2(N)。
但是如果要对AVL树做一些结构修改的操 作性能非常低下比如插入时要维护其绝对平衡旋转的次数比较多更差的是在删除时有可能一直要让旋转持续到根的位置。因此如果需要一种查询高效且有序的数据结构而且数据的个数为静态的即不会改变可以考虑AVL树但一个结构经常修改就不太适合。