【数据结构 -- AVL树】c语言和go语言实现

引言

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(左孩子的右子树高度>左子树)

方法就是

  1. 先将根结点的左孩子左旋
  2. 再将根结点右旋

对于这个二叉树,调整过程:

  1. 将根结点的左孩子左旋
    在这里插入图片描述
  2. 将根结点右旋后
    在这里插入图片描述

RL型

unbalanceNode在根结点右孩子的左子树 – 先右旋再左旋(根结点的右孩子右旋,根结点左旋)

如图,插入结点29后导致二叉树失衡,插入的结点29在根结点右孩子48的左子树上,所以就是 RL 型
在这里插入图片描述

LR型的平衡因子满足:
根结点:-2
根结点的右孩子:1(右孩子的左子树高度>右子树)

方法就是

  1. 先将根结点的右孩子右旋
  2. 再将根结点左旋

对于这个二叉树,调整过程:

  1. 将根结点的右孩子右旋
    在这里插入图片描述

  2. 将根结点左旋
    在这里插入图片描述

实现(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)

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值