平衡二叉树 (AVL树)

本文详细介绍了AVL树的概念,作为一种自平衡二叉查找树,AVL树确保了插入和删除操作的时间复杂度为O(logN)。在平衡条件上,AVL树规定每个节点的左右子树高度差不超过1。当插入或删除操作破坏平衡时,通过单旋或双旋操作来恢复平衡。文章提供了AVL树插入、删除和查找操作的C语言实现,并展示了四种旋转情况:左旋、右旋、右左旋和左右旋。
摘要由CSDN通过智能技术生成

我们希望对于二叉查找树,除MakeEmpty外的操作都花费O(logN)时间,但是,由于我们在进行删除操作时,总是用右子树中的一个节点来代替被删除元素,这就会导致在一系列删除之后,左子树的深度要明显的深于右子树,在25万次随机插入/删除之后,图中的树看起来明显的不平衡。

当树不再平衡后,那么每次操作的时间就可能不再是O(logN),这对于性能而言是有害的。一个解决方法就是:要有一个称为平衡的附加结构条件,任何节点的深度不能过深。

AVL树是一种典型的平衡二叉树。

AVL树是带有平衡条件的二叉查找树。这个平衡条件必须要容易保持,最简单的想法是使左右子树具有相同的高度,这种想法并不要求树的深度要浅,另一种想法是要求每一节点的左右子树都具有相同的高度,这个平衡条件虽然保证了树的深度浅,但是实现起来困难,所以在放宽条件后,给出了一种平衡条件,即一棵AVL树是每个节点的左右子树高度最多差1的二叉查找树,如图

而这样的树就不是一棵AVL树,因为它的右子树高度为0,而左子树的高度为2.

当插入一个新的节点时,就可能会造成平衡性的破坏,但是,总可以通过对树的一些简单操作来恢复其平衡状态,我们称这种操作为旋转。在插入以后,只有从插入节点到根节点的路径上的节点的平衡可能被改变,因为只有这些节点的子树发生了变化,当沿着这条路径更新平衡信息时,可以找到一个节点的新平衡破坏了AVL的条件(即最深的节点),在这个节点上进行旋转就可以重新平衡二叉树。

我们把破坏平衡条件的最深的那个节点称为alpha。由于任意节点最多有两个孩子,所以当不平衡时,左右子树的高度差为2,不平衡有四种情况:

1.对alpha的左儿子的左子树进行一次插入

2.对alpha的左儿子的右子树进行一次插入

3.对alpha的右儿子的左子树进行一次插入

4.对alpha的右儿子的右子树进行一次插入

情形1和4是镜像对称的,2和3是镜像对称的,但它们的实现在程序上还是四种情形。情形1和4可以看作是插入在外面的情况,可以用一次单旋转来调整。3和2可以看作是插入在内部的情况,可以用一次双旋转来调整。下面介绍单旋转和双旋转:

单旋转:

情形1如图,节点k2不满足平衡条件,那么只需将k1移动到k2的位置,并使k1的右子树成为k2的左子树,k2成为k1的右子树即可。这个过程可以想象成树是柔软的,捏住k1节点进行抖动,在重力的作用下,k1就变成了新的根。

情形4如图,节点k2不满足平衡条件,那么只需要将k1移动到k2的位置,并使k1的左子树成为k2的右子树,而k2成为k1的左子树即可。

双旋转:

情形2如图,节点k3不满足平衡条件,只需要使k2移动到k3的位置,k2的左子树成为k1的右子树,k2的右子树成为k3的左子树,k1成为k2的左子树,k3成为k2的右子树即可。

情形3如图,节点k3不满足平衡条件,只需要使k2移动到k3的位置,k2的左子树成为k3的右子树,k2的右子树成为k1的左子树,k1成为k2的右子树,k3成为k2的左子树即可。

通过实践可以发现,双旋转实际上可以由两次单旋转来实现,所以在编程中,双旋转只需要直接直接调用两次单旋转即可实现。

AVL树的具体实现:

结构定义:

typedef struct AVLTreeNode {
	int val;
	int height;
	struct AVLTreeNode* left;
	struct AVLTreeNode* right;
}AVLTreeNode;

//获得结点高度,因为要处理NULL的情况,所以需要写一个模块来完成
int GetHeight(AVLTreeNode* p) {
	if (!p)
		return 0;
	else
		return p->height;
}

旋转操作:

//左旋
AVLTreeNode* LeftSingleRotate(AVLTreeNode* root) {
	AVLTreeNode* ret = root->right;
	root->right = ret->left;
	ret->left = root;
	root->height = max(GetHeight(root->left), GetHeight(root->right)) + 1;
	return ret;
}

//右旋
AVLTreeNode* RightSingleRotate(AVLTreeNode* root) {
	AVLTreeNode* ret = root->left;
	root->left = ret->right;
	ret->right = root;
	root->height = max(GetHeight(root->left), GetHeight(root->right)) + 1;
	return ret;
}

//右左旋
AVLTreeNode* RightLeftDoubleRotate(AVLTreeNode* root) {
	root->right = RightSingleRotate(root->right);
	root = LeftSingleRotate(root);
	return root;
}

//左右旋
AVLTreeNode* LeftRightDoubleRotate(AVLTreeNode* root) {
	root->left = LeftSingleRotate(root->left);
	root = RightSingleRotate(root);
	return root;
}

插入操作:

//实现平衡二叉树的增删改查,二叉树的建立由向空树中插入实现
//假设树中不能有相等的元素
AVLTreeNode* Insert(AVLTreeNode* root, int val) {
	if (root == NULL) {
		AVLTreeNode* tmp = (AVLTreeNode*)malloc(sizeof(AVLTreeNode));
		if (tmp == NULL) {
			perror("malloc");
			return NULL;
		}
		tmp->val = val;
		tmp->left = tmp->right = NULL;
		root = tmp;
	}
	else {
		if (val == root->val) {
			return root;
		}
		else {
			if (val > root->val) {
				root->right = Insert(root->right, val);
				if (GetHeight(root->right) - GetHeight(root->left) == 2) {
					if (val > root->right->val) {
						root = LeftSingleRotate(root);
					}
					else {
						root = RightLeftDoubleRotate(root);
					}
				}
			}
			else {
				root->left = Insert(root->left, val);
				if (GetHeight(root->left) - GetHeight(root->right) == 2) {
					if (val < root->left->val) {
						root = RightSingleRotate(root);
					}
					else {
						root = LeftRightDoubleRotate(root);
					}
				}
			}
		}
	}
	root->height = max(GetHeight(root->left), GetHeight(root->right)) + 1;
	return root;
}

删除操作:

//删除
//删除分为三种情况:删除结点无子树,有一个子树,有两个子树
//对于无子树和一个子树的情况,删除后并不会改变当前结点以下结点的高度,所以失衡最早发生在当前结点
//对于两个子树而言,实际上转换为删除有一个子树或者无子树的情况
//所以对于失衡的判断或高度的更新可以放在本模块结尾
AVLTreeNode* Delete(AVLTreeNode* root, int val) {
	if (root == NULL) {
		printf("树为空或删除元素不存在\n");
		return NULL;
	}
	else {
		if (root->val == val) {
			if (root->left == NULL && root->right == NULL) {
				free(root);
				root = NULL;
			}
			else if (root->left == NULL||root->right == NULL) {
				AVLTreeNode* ret = root->left ? root->left : root->right;
				free(root);
				root = ret;
			}
			else {
				//找到root的中序前驱
				AVLTreeNode* tmp = root->left;
				while (tmp->right)
					tmp = tmp->right;
				root->val = tmp->val;
				root->left = Delete(root->left, root->val);
			}
		}
		else if (root->val > val) {
			root->left = Delete(root->left, val);
		}
		else {
			root->right = Delete(root->right, val);
		}
	}
	if (root) {
		//判断这个结点是否失衡
		if (abs(GetHeight(root->left) - GetHeight(root->right)) == 2) {
			//失衡后判断可以转换为哪种插入情况
			//向左子树插入的情况
			if (GetHeight(root->left) > GetHeight(root->right)) {
				//判断是左左还是左右,可以根据左子树的左右子树的高度判断,当相等时认为是左左,因为单旋转效率较高
				if (GetHeight(root->left->left) >= GetHeight(root->left->right)) {
					root = RightSingleRotate(root);
				}
				else {
					root = LeftRightDoubleRotate(root);
				}
			}
			else {
				//判断是右右还是右左
				if (GetHeight(root->right->right) >= GetHeight(root->right->left)) {
					root = LeftSingleRotate(root);
				}
				else {
					root = RightLeftDoubleRotate(root);
				}
			}
		}
		root->height = max(GetHeight(root->left), GetHeight(root->right)) + 1;
	}
	return root;
}

根据关键字查找某个结点:

//查找某个值的结点
AVLTreeNode* FindKey(AVLTreeNode* root, int key) {
	if (!root) {
		printf("树为空或元素不存在\n");
		return NULL;
	}
	else {
		if (root->val == key) {
			return root;
		}
		else if (root->val > key) {
			return FindKey(root->left, key);
		}
		else {
			return FindKey(root->right, key);
		}
	}
}

测试代码:

#include <stdio.h>
#include <stdlib.h>

//中序遍历
void InOrderPrint(AVLTreeNode* root) {
	if (root) {
		InOrderPrint(root->left);
		printf("%d ", root->val);
		InOrderPrint(root->right);
	}
}

int main() {

	AVLTreeNode* root = NULL;
	for (int i = 0; i <= 10; i++) {
		root = Insert(root, i);
	}

	for (int i = 0; i <= 10; i++) {
		printf("查找%d的结果为:%d\n", i, FindKey(root, i)->val);
	}

	/*for (int i = 0; i <= 10; i++) {
		root = Delete(root, i);
		InOrderPrint(root);
		printf("\n----------------------\n");
	}*/

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值