在本文中,我们涉及了二叉搜索树的相关知识,如果想要对二叉搜索树进行学习,可以查看:
【数据结构】BST(二叉查找树/二叉搜索树)
平衡二叉树(Balanced Binary Tree)又称为自平衡二叉查找树,英文缩写为AVL,这是因为它是由 G. M. Adelson-Velsky 和 E.M.Landis于1962年提出的。
在学习平衡的概念时,我们需要明确平衡二叉树AVL首先是一棵二叉搜索树。那么平衡又是指什么呢?
我们知道结点的高度就是从根结点到该结点的边的总和,而节点的平衡因子指的是该结点的左子树和右子树的高度差,当一个节点的平衡因子为1、0、-1时我们就说该结点是平衡的,也就是说该结点的左右子树高度差不超过1。
当一棵树的任意一个节点都是平衡的时,那么我们就说这棵树是一棵平衡二叉树。
我们给出一张对比图,如下:
我们观察图右中树的根结点,它的左子树高度为3,右子树高度为1,此时根结点的平衡因子为2,显然该结点是不平衡的,所以图右中的树不是一棵平衡二叉树,而图左满足平衡二叉树的条件。
我们知道二叉搜索树BST的优势在于其中序遍历结果时一个有序数列,这种特点使得二叉查找树的能够快速查找,查找的时间复杂度和其高度呈线性关系,也就是说二叉搜索树的高度越低,该树的搜索效率越快。
而具有相同数目节点的树中任意节点的左右子树高度差越小,也就是平衡因子绝对值越小,这棵树的高度就越低,故而我们希望二叉搜索树是平衡的。
我们通过判断树中的每一个节点的平衡因子来判断该树是否是一棵平衡二叉树,具体代码如下:
int depth(TreeNode* root){
if(nullptr == root) return 0;
int leftdepth = depth(root->left)+1;
int rightdepth = depth(root->right)+1;
return max(leftdepth, rightdepth);
}
bool isBalanced(TreeNode* root) {
if(root == nullptr) return true;
int balancee_factor = depth(root->left) - depth(root->right);
if(nullptr != root->left){
if(!isBalanced(root->left)) return false;
}
if(nullptr != root->right){
if(!isBalanced(root->right)) return false;
}
return balancee_factor>=-1 &&balancee_factor<=1;
}
在向平衡二叉树中添加节点很可能会导致这颗二叉树失去平衡,所以我们需要在每次插入节点后进行平衡的维护操作。插入节点破坏平衡性有如下四种情况:
插入一个新节点到不平衡节点的左子树的左子树,导致不平衡结点的平衡因子由1变为2。
旋转操作如下图所示:
Node* RightRotate(Node* root){
if(nullptr == root) return nullptr;
Node* p = root->left;
Node* q = root;
q->left = p->right;
p->right = q;
return p;
}
插入一个新节点到不平衡子树的右子树的右子树,导致不平衡节点的平衡因子由-1变成-2。
旋转操作如下图所示:
Node* LeftRotate(Node* root){
if(nullptr == root) return nullptr;
Node* q = root->right;
Node* p = root;
p->right = q->left;
q->left = p;
return q;
}
插入一个新节点到不平衡的左子树的右子树,导致不平衡结点的平衡因子由1变为2。
旋转操作如下如所示:
Node* LeftRightRotate(Node* root){
if(nullptr == root) return nullptr;
Node* k = root;
Node* p = root->left;
Node* q = p->right;
k->left = LeftRotate(p);
return RightRotate(k);
}
插入一个新节点到不平衡的右子树的左子树,导致不平衡节点的平衡因子由-1变为-2。
旋转操作如下图所示:
Node* RightLeftRotate(Node* root){
if(nullptr == root) return nullptr;
Node* k = root;
Node* p = root->right;
Node* q = p->left;
k->right = RightRotate(p);
return LeftRotate(k);
}
我们知道了当树处于哪种不平衡的状态时如何处理,那么我们该怎么判断一棵树处于哪种非平衡状态,其实主要是通过结点的平衡因子来判断的。具体代码如下:
int depth(Node* root){
if(nullptr == root) return nullptr;
return max(depth(root->left), depth(root->right))+1;
}
int BalancedFactory(Node* root){
return depth(root->left)-depth(root->right);
}
Node* Balance(Node* root){
int bf = BalancedFactory(root);
if(bf == 2){
if(BalancedFactory(root->left) == 1){
RightRotate(root);
}else{
LeftRightRotate(root);
}
}else if(bf == -2){
if(BalancedFactory(root->right) == -1){
LeftRotate(root);
}else{
RightLeftRotate(root);
}
}
return root;
}
和搜索二叉树中的插入很相似,唯一不同的是我们需要在插入节点之后需要判断是否平衡,若时不平衡则需要进行旋转,具体代码如下:
Node* Add(Node* node, int val){
if(node == nullptr){
return new Node(val);
}
if(val < node->val){
node->left = Add(node->left, val);
}else{
node->right = Add(node->right, val);
}
Balance(node);
return node;
}
和BST的删除相似,在删除节点之后需要判断是否平衡,若不平衡则需要进行旋转操作,具体代码如下:
Node* Del(Node* node, int val){
if(nullptr == root) return nullptr;
if(node->val == val){
if(nullptr==node->right && nullptr==node->left){
// 叶子节点
delete node;
return nullptr;
}
if(nullptr == node->right){
// 右子树为空
// 直接使用当前待删除节点的左节点代替该结点
Node* p = node->left;
delete node;
return p;
}
if(nullptr == node->left){
// 左子树为空
// 直接使用当前待删除节点的右节点代替该结点
Node* p = node->right;
delete node;
return p;
}
// 左右孩子都有时
// 直接使用该节点为根结点的右子树的最小值代替当前节点
Node* maxNode = Min(node->right);
node->val = maxNode->val;
node->right = Del(node->right, maxNode->val);
}else if(node->val > val){
node->left == Del(node->left, val);
}else{
node->right == Del(node->right, val);
}
return Balanced(node);
}