赞
踩
二叉搜索树虽然可以缩短查找的效率,但如果数据有序或者接近有序的二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此俄罗斯的两位数学家 G.M.Adelson-Velskii 和 E.M.Landis 在1962年发明了一种解决上述问题的方法:
当二叉搜索树插入新节点后,如果能保证每个节点的左右子树高度之差 的绝对值不超过1,即可降低树的高度,从而减少平均搜索长度。
一颗AVL树或者是空树 或者具有以下性质的搜索二叉树:
AVL树平均高度大概是1.44logN + c,c是常数,搜索的时间复杂度是 o(logn)
template<class K ,class V> struct AVLTreeNode { pair<K, V> _kv; AVLTreeNode<K, V>* _left; AVLTreeNode<K, V>* _right; AVLTreeNode<K, V>* _parent; int _bf; // balance factor AVLTreeNode(const pair<K,V>& kv = pair<K,V>()) :_left(nullptr) , _right(nullptr) , _parent(nullptr) , _bf(0) ,_kv(kv) {} };
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么
AVL树的插入过程可以分为两步:
1. 按照二叉搜索树的方式插入新节点
2. 调整节点的平衡因子
bool Insert(const pair<K,V>& kv) { if (_root == nullptr) { _root = new Node(kv); _root->_bf = 0; 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 (parent->_kv.first > kv.first) { parent->_left = cur; } else { parent->_right = cur; } cur->_parent = parent; //更新bf; while (parent) { if (cur == parent->_left) { parent->_bf--; } else if (cur == parent->_right) { parent->_bf++; } if (parent->_bf == 0) { break; } else if (parent->_bf == 1 || parent->_bf == -1) { cur = cur->_parent; parent = parent->_parent; //向上调查一下 } else if (parent->_bf == 2 || parent->_bf == -2) { //出问题,需要旋转! break; } else { cout << "平衡因子出现异常" << endl; assert(false); } } return true; }
如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,
使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
1. 新节点插入较高左子树的左侧—左左:右单旋
void RotateR(Node* parent) { Node* ppNode = parent->_parent;//记录原parent节点的parent Node* subL = parent->_left; subL为左根 Node* subLR = subL->_right;subLR为左根的右子树根 parent->_left = subLR; if (subLR) //subLR有可能为空 subLR->_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; } //更新平衡因子 balance factor subL->_bf = 0; parent->_bf = 0; }
2. 新节点插入较高右子树的右侧—右右:左单旋
void RotateL(Node* parent) { Node* ppNode = parent->_parent; Node* subR = parent->_right; Node* subRL = subR->_left; parent->_right = subRL; if (subRL) { subRL->_parent = parent; } subR->_left = parent; parent->_parent = subR; if (parent == _root) { _root = subR; _root->_parent = nullptr; } else { if (ppNode->_left == parent) { ppNode->_left = subR; } else { ppNode->_right = subR; } subR->_parent = ppNode; } parent->_bf = 0; subR->_bf = 0; }
3. 新节点插入较高左子树的右侧—左右:先左单旋再右单旋
void RotateRL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; int bf = subRL->_bf; RotateR(parent->_right); //复用 RotateL(parent); if (bf == 0) { subRL->_bf = parent->_bf = subR->_bf = 0; } else if (bf == 1) { subRL->_bf = 0; parent->_bf = -1; subR->_bf = 0; } else if (bf == -1) { subRL->_bf = 0; parent->_bf = 0; subR->_bf = 1; } else { assert(-1); } }
4. 新节点插入较高右子树的左侧—右左:先右单旋再左单旋
void RotateLR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; int bf = subLR->_bf; RotateL(parent->_left); RotateR(parent); if (bf == 0) { parent->_bf = subL->_bf = 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(0); } }
参考右左双旋。
总结:
假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑
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); }
AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这
样可以保证查询时高效的时间复杂度,即
l
o
g
2
(
N
)
log_2 (N)
log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。