引言
AVL树,基于二叉搜索树通过平衡得到
前面我们知道,通过🔗二叉搜索树可以便捷快速地查找到数据,但是当序列有序时,就会退化成如下图所示的单链表,搜索效率降低为O(N),为了解决这个问题,引出了平衡二叉树(AVL树)
定义
平衡二叉树,简称AVL树,它可以是一颗空树
如果不是空树,则需要满足 任何一个结点的左子树与右子树高度之差的绝对值不超过1
图一是AVL树
图二不是AVL树,因为虚线内部分高度差大于1
如何让二叉树变成AVL树呢
答案是通过旋转(左旋、右旋)操作,在说左旋和右旋操作之前,了解一个概念——平衡因子
是否为AVL树需要根据结点的左右子树高度差来判断,所以引出平衡因子的概念
平衡因子:结点左子树高度减去右子树高度
之后我们还可以通过平衡因子来判断需要哪种旋转方式(左旋、右旋)
将二叉树分为四种类型,分别是LL(left)型、RR(right)型、
LR型、RL型
这几种类型是根据引起不平衡的结点的位置来分的,下面将引起不平衡的这个结点叫做unbalanceNode
旋转方式
判断右旋还是左旋,可以这样理解
向哪个方向旋转就是让哪边树更高。比如左旋,就是右子树高于左子树,要想平衡就要让左子树更高一点
LL型
unbalanceNode在根结点左孩子的左子树 – 根结点右旋
如图,插入结点5后导致二叉树失衡,插入的结点5在根结点左孩子14的左子树上,所以就是 LL 型
LL型二叉树的平衡因子满足:
根结点:2
根结点的左孩子:1(左孩子的左子树高度>右子树)
方法就是将根结点右旋,意思就是将根结点向右旋转到其左孩子的右孩子的位置
在根结点向右旋转的过程中,因为根结点的左孩子14原本有右孩子20,所以根结点就会和20发生冲突,这时需要将14的右孩子20变成根结点的左孩子
根结点25旋转完成后就是
再和14相连,最终结果:
如果出现了多个结点都失衡的情况,如下图
46、35、24都失衡了,那这时候就不是将根结点右旋了,而是将 与导致失衡的结点(15)最近的失衡结点右旋,在这个例子中也就是将24右旋
再与35相连,最终结果:
RR型
unbalanceNode在根结点右孩子的右子树 – 根结点左旋
RR 型与 LL 型方法一致,只是换汤不换药
如图,插入结点67后导致二叉树失衡,插入的结点67在根结点右孩子45的右子树上,所以就是 RR 型
RR型二叉树的平衡因子满足:
根结点:-2
根结点的左孩子:-1(左孩子的右子树高度>左子树)
方法就是将根结点左旋,意思就是将根结点向左旋转到其右孩子的左孩子的位置
在根结点向左旋转的过程中,因为根结点的右孩子45原本有左孩子34,所以根结点就会和34发生冲突,这时需要将45的左孩子34变成根结点的右孩子
根结点26旋转完成后就是
再和45相连,最终结果:
如果同时出现了多个失衡结点,和 LL 型一样,也是找到与导致失衡结点距离最近的失衡的结点,对该结点进行左旋操作
LR型
unbalanceNode在根结点左孩子的右子树 – 先左旋再右旋(根结点的左孩子左旋,根结点右旋)
如图,插入结点40后导致二叉树失衡,插入的结点40在根结点左孩子25的右子树上,所以就是 LR 型
LR型的平衡因子满足:
根结点:2
根结点的左孩子:-1(左孩子的右子树高度>左子树)
方法就是
- 先将根结点的左孩子左旋
- 再将根结点右旋
对于这个二叉树,调整过程:
- 将根结点的左孩子左旋
- 将根结点右旋后
RL型
unbalanceNode在根结点右孩子的左子树 – 先右旋再左旋(根结点的右孩子右旋,根结点左旋)
如图,插入结点29后导致二叉树失衡,插入的结点29在根结点右孩子48的左子树上,所以就是 RL 型
LR型的平衡因子满足:
根结点:-2
根结点的右孩子:1(右孩子的左子树高度>右子树)
方法就是
- 先将根结点的右孩子右旋
- 再将根结点左旋
对于这个二叉树,调整过程:
-
将根结点的右孩子右旋
-
将根结点左旋
实现(c语言)
这篇的代码是改过的,因为觉得最初的代码不太好理解,对于什么时候用方法、什么时候传参并不能很明确,所以改成了全部传参的形式,并且添加了 C 语言实现的版本
结构
结构体中一定包含的是数据data 和左右孩子指针
又因为需要计算平衡因子,所以需要知道左右子树的高度,直接将高度height 包含在结构体中
// 定义树结构
typedef struct AVLTree {
int val;
struct AVLTree* left;
struct AVLTree* right;
int height;
} AVLTree;
获取结点高度
首先判断结点 t 为不为 nil, 为 nil 直接返回0
// 获取结点高度
int getHeight(AVLTree* node) {
if (node == NULL) {
return 0;
}
return node->height;
}
平衡因子
平衡因子 = 左子树高度 - 右子树高度
若结点 t 为 nil ,直接返回0
// 计算结点的平衡因子 -- 左子树高度-右子树高度
int getBalanceFactor(AVLTree* node) {
if (node == NULL) {
return 0;
}
return getHeight(node->left) - getHeight(node->right);
}
更新高度
在插入或删除数据后,根结点和其他结点的高度都有可能发生变化,所以在插入或删除结点后,需要更新节点的高度,否则在计算平衡因子会出错
// 更新高度
void updateHeight(AVLTree* node) {
int leftHeight = getHeight(node->left);
int rightHeight = getHeight(node->right);
node->height = (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}
左旋
对 node 进行左旋 --> node连接在node.right 的左孩子,如果node.right 原本有左孩子leftChild,那让leftChild 连接到node 的右孩子
// 左旋
AVLTree* leftRotate(AVLTree* root) {
AVLTree* newRoot = root->right;
root->right = newRoot->left;
newRoot->left = root;
updateHeight(root);
updateHeight(newRoot);
return newRoot;
}
右旋
对 node 进行右旋 --> node连接在node.left 的右孩子,如果node.left 原本有右孩子rightChild,那让rightChild 连接到node 的左孩子
// 右旋
AVLTree* rightRotate(AVLTree* root) {
AVLTree* newRoot = root->left;
root->left = newRoot->right;
newRoot->right = root;
updateHeight(root);
updateHeight(newRoot);
return newRoot;
}
插入结点
按照二叉搜索树插入数据的方式插入,再根据平衡因子判断是否需要调整和调整的方式
// 插入结点
AVLTree* insert(AVLTree* node, int val) {
if (node == NULL) {
node = (AVLTree*)malloc(sizeof(AVLTree));
node->val = val;
node->left = node->right = NULL;
node->height = 1;
return node;
}
if (val < node->val) {
node->left = insert(node->left, val);
}
else if (val > node->val) {
node->right = insert(node->right, val);
}
else {
return node; // 不允许插入重复元素
}
updateHeight(node);
int balance = getBalanceFactor(node);
if (balance > 1) {
if (val < node->left->val) {
return rightRotate(node); // LL型
}
else {
node->left = leftRotate(node->left); // LR型
return rightRotate(node);
}
}
if (balance < -1) {
if (val > node->right->val) {
return leftRotate(node); // RR型
}
else {
node->right = rightRotate(node->right); // RL型
return leftRotate(node);
}
}
return node;
}
删除结点
和二叉搜索树删除结点的方法一样,只是最后需要更新结点高度,并判断是否需要进行调整
// 删除节点
AVLTree* delete(AVLTree* root, int val) {
if (root == NULL) {
return NULL;
}
if (val < root->val) {
root->left = delete(root->left, val);
}
else if (val > root->val) {
root->right = delete(root->right, val);
}
else {
if (root->left == NULL && root->right == NULL) {
free(root);
return NULL;
}
if (root->left == NULL) {
AVLTree* temp = root->right;
free(root);
return temp;
}
if (root->right == NULL) {
AVLTree* temp = root->left;
free(root);
return temp;
}
// 节点有两个子节点
AVLTree* temp = behind(root->right);
root->val = temp->val;
root->right = delete(root->right, temp->val);
}
updateHeight(root);
int balance = getBalanceFactor(root);
if (balance > 1) {
if (getHeight(root->left->left) > getHeight(root->left->right)) {
return rightRotate(root); // LL型
}
else {
root->left = leftRotate(root->left); // LR型
return rightRotate(root);
}
}
if (balance < -1) {
if (getHeight(root->right->right) > getHeight(root->right->left)) {
return leftRotate(root); // RR型
}
else {
root->right = rightRotate(root->right); // RL型
return leftRotate(root);
}
}
return root;
}
// 获取最小节点(中序后继)
AVLTree* behind(AVLTree* node) {
AVLTree* current = node;
while (current && current->left != NULL) {
current = current->left;
}
return current;
}
中序遍历
// 中序遍历
void inorder(AVLTree* root) {
if (root == NULL) {
return;
}
inorder(root->left);
printf("%d ", root->val);
inorder(root->right);
}
测试样例
通过观察 AVL 树的中序遍历及根结点来判断是否正确执行了删除、插入节点操作
int main() {
AVLTree* root = NULL;
// 插入节点
root = insert(root, 46);
root = insert(root, 35);
root = insert(root, 37);
root = insert(root, 20);
root = insert(root, 42);
root = insert(root, 66);
root = insert(root, 15);
root = insert(root, 24);
// 输出根结点和中序遍历
printf("中序遍历:");
printf("根结点:%d", root->val);
inorder(root);
printf("\n");
// 删除 24
printf("\n删除 24 后\n");
root = delete(root, 24);
printf("中序遍历:");
printf("根结点:%d", root->val);
inorder(root);
printf("\n");
// 删除 20
printf("\n删除 20 后\n");
root = delete(root, 20);
printf("中序遍历:");
printf("根结点:%d", root->val);
inorder(root);
printf("\n");
// 删除 15
printf("\n删除 15 后\n");
root = delete(root, 15);
printf("中序遍历:");
printf("根结点:%d", root->val);
inorder(root);
printf("\n");
// 删除 35
printf("\n删除 35 后\n");
root = delete(root, 35);
printf("中序遍历:");
printf("根结点:%d", root->val);
inorder(root);
printf("\n");
return 0;
}
实现(go语言)
上面已经有过对代码的解释,所以就不再解释了,直接写代码
// 结构
type AVLTree struct {
Val int
Left *AVLTree
Right *AVLTree
Height int //树高
}
// 获取结点高度
func GetHeight(t *AVLTree) int {
if t == nil {
return 0
}
return t.Height
}
// 获取平衡因子
func GetBalanceFactor(t *AVLTree) int {
if t == nil {
return 0
}
return GetHeight(t.Left) - GetHeight(t.Right)
}
// 更新高度
func UpdateHeight(t *AVLTree) {
//比较左子树和右子树的高度,取大的那一个加一
left := GetHeight(t.Left)
right := GetHeight(t.Right)
if left > right {
t.Height = left + 1
} else {
t.Height = right + 1
}
}
// 左旋
func LeftRotate(root *AVLTree) *AVLTree {
//成为她右孩子的左孩子
newRoot := root.Right
root.Right = newRoot.Left
newRoot.Left = root
//更新高度
UpdateHeight(root)
UpdateHeight(newRoot)
//返回
return newRoot
}
// 右旋
func RightRotate(root *AVLTree) *AVLTree {
newRoot := root.Left
root.Left = newRoot.Right
newRoot.Right = root
UpdateHeight(root)
UpdateHeight(newRoot)
return newRoot
}
// 插入结点
func Insert(t *AVLTree, val int) *AVLTree {
if t == nil {
return &AVLTree{
val,
nil,
nil,
1,
}
}
//找插入结点位置
if val < t.Val { //小于t,向t的左子树中插入
t.Left = Insert(t.Left, val)
} else if val > t.Val {
t.Right = Insert(t.Right, val)
} else {
return t //要求数据不可以重复
}
//插入后,更新高度,并且检查是否失衡,需要调整
UpdateHeight(t) //更新高度
balance := GetBalanceFactor(t) //获取平衡因子
if balance > 1 { //失衡,L_型
if GetHeight(t.Left.Left) > GetHeight(t.Left.Right) { //左子树高,LL型 -- t右旋
return RightRotate(t)
} else { //LR型 -- 左孩子左旋,t右旋
LeftRotate(t.Left)
return RightRotate(t)
}
}
if balance < -1 { //R_型
if GetHeight(t.Right.Right) > GetHeight(t.Right.Left) { //RR型
return LeftRotate(t)
} else {
RightRotate(t.Right)
return LeftRotate(t)
}
}
return t
}
// 删除结点
func Delete(t *AVLTree, val int) *AVLTree {
if t == nil {
return nil
}
if val < t.Val {
t.Left = Delete(t.Left, val)
} else if val > t.Val {
t.Right = Delete(t.Right, val)
} else {
//叶子结点
if t.Left == nil && t.Right == nil {
return nil
}
//只有左孩子或右孩子
if t.Left == nil {
return t.Right
}
if t.Right == nil {
return t.Left
}
//左右孩子都有
behind := Behind(t.Right)
t.Val = behind.Val
Delete(t.Right, behind.Val)
}
//更新高度
UpdateHeight(t) //更新高度
balance := GetBalanceFactor(t) //获取平衡因子
if balance > 1 { //失衡,L_型
if GetHeight(t.Left.Left) > GetHeight(t.Left.Right) { //左子树高,LL型 -- t右旋
return RightRotate(t)
} else { //LR型 -- 左孩子左旋,t右旋
t.Left = LeftRotate(t.Left)
return RightRotate(t)
}
}
if balance < -1 { //R_型
if GetHeight(t.Right.Right) > GetHeight(t.Right.Left) { //RR型
return LeftRotate(t)
} else {
t.Right = RightRotate(t.Right)
return LeftRotate(t)
}
}
return t
}
// 中序后继
func Behind(t *AVLTree) *AVLTree {
if t.Left == nil {
return t
}
return Behind(t.Left)
}
// 中序遍历
func Inorder(t *AVLTree) {
if t == nil {
return
}
Inorder(t.Left)
fmt.Println(t.Val)
Inorder(t.Right)
}
//测试样例:
func main() {
// 46 35 37 20 42 66 15 24
root := &AVLTree{
35,
nil,
nil,
1,
}
root = Insert(root, 37)
root = Insert(root, 20)
root = Insert(root, 42)
root = Insert(root, 66)
root = Insert(root, 14)
root = Insert(root, 24)
root = Insert(root, 46)
fmt.Println("根结点:", root.Val)
Inorder(root)
fmt.Println("删除24后")
root = Delete(root, 24)
fmt.Println("根结点:", root.Val)
Inorder(root)
fmt.Println("删除20后")
root = Delete(root, 20)
fmt.Println("根结点:", root.Val)
Inorder(root)
fmt.Println("删除14后")
root = Delete(root, 14)
fmt.Println("根结点:", root.Val)
Inorder(root)
fmt.Println("删除35后")
root = Delete(root, 35)
fmt.Println("根结点:", root.Val)
Inorder(root)
}