目录
一. 概念
二. 节点的定义
三. 基础框架
四. 插入
1.左旋
2.右旋
3.右左旋
4.左右旋
5.总览
五. 验证
一. 概念
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
简单来说,AVL树是一个key-value模型,每个子树都是AVL树,且左右子树的高度差不大于1
二. 节点的定义
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; AVLTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _bf(0){}
};
首先,我们使用的是一个三叉链的节点(左子、右子、双亲),而_kv存储的就是key和value,我们在此基础上又新增了一个变量_bf,意为balance factor,即平衡因子,来存储左右子树的高度差(右子树高度-左子树高度,因此可以为负数)。而由于左右子树高度差不大于1,因此_bf的值应该在-1到1之间
三. 基础框架
template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:AVLTree():_root(nullptr){}
private:Node* _root;
};
四. 插入
学习AVL树最重要的就是插入的实现
首先,我们需要依照普通二叉搜索树的插入方式进行插入,之后再进行调整,我们需要从根节点不断向下迭代,知道在合适的叶节点的左(右)节点进行插入,而AVL树同样不允许存在数据的重复,因此在迭代的过程中,我们需要在与节点存储的key相等时结束迭代,返回false
而由于我们在向下迭代时,若是能正确的插入,会从根节点迭代到叶子结点的左(右)节点,而这个节点本身是不存在的,无法通过节点中的_parent来寻找双亲节点进行插入,因此我们在一开始就需要一个设置另一个节点作为双亲进行迭代
bool Insert(const pair<K, V>& kv){if (_root == nullptr)_root = new Node(kv);Node* parent = nullptr;Node* cur = _root;//找位置while (cur){parent = cur;if (kv.first < cur->_kv.first)cur = cur->_left;else if (kv.first > cur->_kv.first)cur = cur->_right;elsereturn false;}//插入cur = new Node(kv);if (parent->_kv.first < cur->_kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}
}
在插入之后,我们就需要进行调整了
首先我们需要对插入节点的祖先们的平衡因子进行更改
while (parent)
{if (cur == parent->_left)parent->_bf--;elseparent->_bf++;
}
在更新好平衡因子后,我们需要根据平衡因子来判断是否还需要向上迭代以及是否需要调整节点的位置
首先若是平衡因子变为0,那么一定是从1/-1变为的0,即在高度较小的子树中插入一个节点,使得左右子树高度相同,若是这种情况,那么该子树的高度是不变的,因此就不需要向上遍历
if (parent->_bf == 0)break;
而若是平衡因子为1或-1,只能是从0变为1/-1的,即从一个左右子树高度相同的树中在左(右)子树中插入了一个节点,因此该子树总高度固定增1,需要继续向上判断
else if (parent->_bf == 1 || parent->_bf == -1)
{cur = parent;parent = parent->_parent;
}
而还剩余一种情况为2或-2,即一个树的左右子树高度差本为1,在高度较大的子树中进行插入,使得其不符合AVL树,这时我们就需要根据情况进行节点位置的调整,我们采用的方法为旋转。当然,若是平衡因大于3或小于-3,说明前面就出问题了,可以直接通过assert(false)报错
那么若是平衡因子为2或-2,我们可以先大致分为两种情况
而这两种情况也可以写作这样
这样,我们就可以把它们分成四种情况,分别在节点2的左右子树进行插入
我们来分别看一看这四种情况
1.左旋
首先是这种,这种情况转换一下就是parent节点的_bf为2,parent的右节点的_bf为1
称作左旋,即将节点1旋转为节点2的左节点
首先我们先把子树2拿出来,之后将节点1作为1节点2的左节点,最后将子树2作为节点1的右子树
当然要注意的是,若是节点1存在双亲节点,需要在旋转之后将双亲节点和节点2链接起来
同时,当子树2不存在时,不需要也不能将子树2的_parent链接到节点1
位置调整完毕后,我们还需要将_bf进行更改,据图观察,节点1、2的_bf都为0,
而总高度在插入之前为h+1,插入并调整之后依然是h+1,也就不需要继续向上调整
void RotateL(Node* parent)
{Node* par_parent = parent->_parent;Node* right=parent->_right;Node* right_left = right->_left;parent->_right = right_left;if(right_left) //right_left可能为空right_left->_parent = parent;right->_left = parent;parent->_parent = right;if (par_parent) //parent不为根{if (parent->_kv.first < par_parent->_kv.first)par_parent->_left = right;elsepar_parent->_right = right;}else //parent为根{_root = right;}right->_parent = par_parent;parent->_bf = right->_bf = 0;
}
2.右旋
我们再来看一种类似的情况
其实就是上面的情况反过来,而采用的方法也类似,进行右旋,把子树2拿出来,将节点1作为节点2的右节点,之后将子树2作为节点1的左子树
同样需要注意的点也和上面一样
void RotateR(Node* parent)
{Node* par_parent = parent->_parent;Node* left = parent->_left;Node* left_right = left->_right;parent->_left = left_right;if(left_right) //left_right可能为空left_right->_parent = parent;left->_right = parent;parent->_parent = left;if (par_parent) //parent不为根{if (parent->_kv.first < par_parent->_kv.first)par_parent->_left = left;elsepar_parent->_right = left;}else //parent为根{_root = left;}left->_parent = par_parent;parent->_bf = left->_bf = 0;
}
而剩下的两种情况相对较为复杂,但这两种情况也是类似的
3.右左旋
首先是这种
而这种情况,我们还可以分为3种
而这三种方法可以通过节点3的_bf来区分,分别为0、-1、1
它们在位置调整时的方式都是一致的,需要进行双旋(右-左),而不同的是_bf的调整
而双旋(右-左),需要先对节点2进行右旋,之后对节点1进行左旋
当然,我们可以复用一下上面的左旋和右旋
而在调整_bf时,首先第一种三个节点的_bf都为0,
第二种子树1、2、4的高度为h,子树3的高度为h-1,因此节点1和3的_bf为0,节点2的_bf为1
第三种子树1、3、4的高度为h,子树2的高度为h-1,因此节点2和3的_bf为0,节点1的_bf为-1
void RotateRL(Node* parent)
{Node* right = parent->_right;Node* right_left = right->_left;int bf = right_left->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){parent->_bf = 0;right->_bf = 0;right_left->_bf = 0;}else if (bf == 1){parent->_bf = -1;right->_bf = 0;right_left->_bf = 0;}else if (bf == -1){parent->_bf = 0;right->_bf = 1;right_left->_bf = 0;}else{assert(false);}
}
4.左右旋
最后就剩下一种情况了
void RotateLR(Node* parent)
{Node* left = parent->_left;Node* left_right = left->_right;int bf = left_right->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){parent->_bf = 0;left->_bf = 0;left_right->_bf = 0;}else if (bf == 1){parent->_bf = 0;left->_bf = -1;left_right->_bf = 0;}else if (bf == -1){parent->_bf = 1;left->_bf = 0;left_right->_bf = 0;}else{assert(false);}
}
5.总览
bool Insert(const pair<K, V>& kv)
{if (_root == nullptr)_root = new Node(kv);Node* parent = nullptr;Node* cur = _root;//找位置while (cur){parent = cur;if (kv.first < cur->_kv.first)cur = cur->_left;else if (kv.first > cur->_kv.first)cur = cur->_right;elsereturn false;}//插入cur = new Node(kv);if (parent->_kv.first < cur->_kv.first){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}while (parent){if (cur == parent->_left)parent->_bf--;elseparent->_bf++;if (parent->_bf == 0)//parent两个子树高度相同,即在高度较小的子树中进行的插入,不需要再向上判断break;else if (parent->_bf == 1 || parent->_bf == -1)//parent本来两个子树高度相同,在任意子树进行了插入,总高度增加,向上判断{cur = parent;parent = parent->_parent;}//_bf为正负2,树变得不平衡,需要对结构进行调整else if(parent->_bf == 2){if (cur->_bf == 1){RotateL(parent);}else if (cur->_bf == -1){RotateRL(parent);}else{assert(false);}break;}else if (parent->_bf == -2){if (cur->_bf == 1){RotateLR(parent);}else if (cur->_bf == -1){RotateR(parent);}else{assert(false);}break;}else//当不为上述值时说明在处理_bf前就出现了问题{assert(false);}}return true;
}void RotateL(Node* parent)
{Node* par_parent = parent->_parent;Node* right=parent->_right;Node* right_left = right->_left;parent->_right = right_left;if(right_left)//right_left可能为空right_left->_parent = parent;right->_left = parent;parent->_parent = right;if (par_parent)//parent不为根{if (parent->_kv.first < par_parent->_kv.first)par_parent->_left = right;elsepar_parent->_right = right;}else//parent为根{_root = right;}right->_parent = par_parent;parent->_bf = right->_bf = 0;
}void RotateR(Node* parent)
{Node* par_parent = parent->_parent;Node* left = parent->_left;Node* left_right = left->_right;parent->_left = left_right;if(left_right)//left_right可能为空left_right->_parent = parent;left->_right = parent;parent->_parent = left;if (par_parent)//parent不为根{if (parent->_kv.first < par_parent->_kv.first)par_parent->_left = left;elsepar_parent->_right = left;}else//parent为根{_root = left;}left->_parent = par_parent;parent->_bf = left->_bf = 0;
}void RotateLR(Node* parent)
{Node* left = parent->_left;Node* left_right = left->_right;int bf = left_right->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){parent->_bf = 0;left->_bf = 0;left_right->_bf = 0;}else if (bf == 1){parent->_bf = 0;left->_bf = -1;left_right->_bf = 0;}else if (bf == -1){parent->_bf = 1;left->_bf = 0;left_right->_bf = 0;}else{assert(false);}
}void RotateRL(Node* parent)
{Node* right = parent->_right;Node* right_left = right->_left;int bf = right_left->_bf;RotateR(parent->_right);RotateL(parent);if (bf == 0){parent->_bf = 0;right->_bf = 0;right_left->_bf = 0;}else if (bf == 1){parent->_bf = -1;right->_bf = 0;right_left->_bf = 0;}else if (bf == -1){parent->_bf = 0;right->_bf = 1;right_left->_bf = 0;}else{assert(false);}
}
五. 验证
首先,我们可以先采用优先级队列的方式(即中序遍历观察是否顺序)验证一下是否为二叉搜索树
void InOrder()
{_InOrder(_root);
}void _InOrder(Node* root)
{if (root == NULL)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);
}
之后,由于我们在插入中已经进行过平衡因子的判断,我们可以只需要判断一下平衡因子是否正确
我们可以采用递归的方式从下向上得到每个子树的高度以及平衡因子是否正确
我们将返回值设为树的高度,来进行向上递归的判断,同时当平衡因子出现错误,我们可以返回-1作为标记。
bool IsBalance()
{int ans = _IsBalance(_root);if (ans == -1)return false;elsereturn true;
}int _IsBalance(Node* root)
{if (!root)return 0;int left=_IsBalance(root->_left);int right= _IsBalance(root->_right);if (left == -1 || right == -1) //子树的_bf已经出错,直接返回-1return -1;if (right - left != root->_bf) //出错返回-1return -1;else //没出错返回left与right的最大值+1,表示该树的高度return (left > right ? left : right)+1;
}
end