AVL树

AVL树的产生

对于一棵二叉搜索树,它的查找、删除、插入的平均时间复杂度均为O(logn),但如果我们把这棵树一侧的结点全部删除,它就退化成了一个链表,查找、删除、插入的平均时间复杂度就变成了O(n)
在这里插入图片描述
因此我们希望设计一种树形数据结构,使得它的时间复杂度保持在O(logn),不会退化成链表。AVL树就是这样的一种数据结构。

AVL树的定义

  1. AVL树是一棵二叉搜索树
  2. 每个结点的左右子树高度差(平衡因子)的绝对值小于2

AVL树其实就是一棵自平衡二叉搜索树。

AVL树的实现

在阅读本文时,你至少要对二叉搜索树和c++有一定的了解。

类定义

本文的AVL树使用如下的类定义:

template <typename Type> class AVLTree	//AVL树模板类
{
public:
	AVLTree();	//构造函数
	AVLTree(Type val);	//构造函数
	~AVLTree();	//析构函数
	bool empty();	//判断树是否为空
	void clear();	//清空整棵树
	AVLTree* Find(Type& val);	//查找
	bool Insert(Type& val);	//插入
	bool Delete(Type& val);	//删除

private:
	Type val;	//根结点存储的数据
	AVLTree* left;	//左子树
	AVLTree* right;	//右子树
	int height;	//树的高度
	Type takeOutMin();	//取出树中最小值
	void LL();	//LL旋转
	void RR();	//RR旋转
	void LR();	//LR旋转
	void RL();	//RL旋转
};

构造函数如下:

template <typename Type> AVLTree<Type>::AVLTree()	//构造一个空树
{
	left = right = nullptr;	//左右子树为空指针
	height = 0;	//树高为0
	memset(&val, 0, sizeof(Type));	//初始化val为0
}

template <typename Type> AVLTree<Type>::AVLTree(Type val)	//构造一个只含根结点的AVL树
{
	left = new AVLTree();	//左子树为一个空树
	right = new AVLTree();	//右子树为一个空树
	height = 1;	//树高为1
	this->val = val;	//初始化val
}

当这个树为空树时,我们依然用一个结点来保存这个树,只不过这时令树高为0,表示这是一个空树。这样做是为了统一代码风格,不必考虑对空指针的处理,但增加了一倍的内存开销。因此如果对内存限制较高,可以考虑用空指针表示空树,只需对代码进行简单的修改即可。
根据上面的介绍,相信你可以轻松理解下面的几个函数:
判空函数:

template <typename Type> bool AVLTree<Type>::empty()
{
	return height == 0;	//如果树高为0,代表这是一个空树
}

清空函数:

template <typename Type> void AVLTree<Type>::clear()
{
	if (!empty())	//如果树非空,则递归释放其子树的内存
	{
		right->clear();
		left->clear();
	}
	height = 0;	//递归返回后,当前树一定是一个空树,因此令树高为0
	delete this;	//释放当前树的内存空间
}

析构函数:

template <typename Type> AVLTree<Type>::~AVLTree()
{
	if (!empty())	//如果树非空,就释放其子树的内存空间
	{
		right->clear();
		left->clear();
	}
}

旋转

我们将每个结点左右子树高度差称为平衡因子。
想要一棵树保持平衡,只需要其每个结点的平衡因子绝对值小于2,这就是平衡树的定义,也是AVL树定义的第二个条件。平衡树的所有叶结点都在树的最后两层,与完全二叉树相似,因此可以避免自身退化成链表。
AVL树通过旋转来保证平衡因子绝对值小于2,从而实现自身的平衡。AVL树的旋转分成四种:LL(右旋)、LR(左右旋)、RR(左旋)、RL(右左旋),其中LL与RR对称,LR与RL对称。
每当某个结点的平衡因子绝对值等于2时,我们就进行某种旋转操作,使得平衡因子变为0(进行四种旋转操作后,平衡因子一定是0,可以自行思考一下)

LL与RR

在这里插入图片描述
通过上图可以看出,当某个结点A失衡时,如果这个结点失衡的原因是由它左儿子的左子树(即结点C)造成的,那么LL旋转(LL就是Left-Left,左儿子的左子树)可以调整这种失衡,同时保证AVL树其他部分的平衡性质不变。LL旋转可以看作将结点A顺时针旋转,因此又叫做右旋。
LL操作可以分为下面6步:

  1. 保存B
  2. 令A的左儿子指向C
  3. 将B的右儿子放在其左儿子的位置上
  4. 令B的右儿子指向A的右儿子
  5. 令A的右儿子指向B
  6. 交换A和B的val值
    在这里插入图片描述
    旋转后树高可能会被改变,因此,需要调整树高,根据图示可以看出,C结点的子树在旋转前后完全没变,因此被改变的只可能是A、B结点的树高,因此在旋转结束后重新确定A、B树高即可。A结点就是旋转后的右子树,B结点就是旋转后的根结点。一个结点的高度等于其左右结点高度最大值加1。
template <typename Type> void AVLTree<Type>::LL()
{
	if (!empty())
	{
		AVLTree* ptr = left;	//1步
		left = ptr->left;	//2步
		ptr->left = ptr->right;	//3步
		ptr->right = right;	//4步
		right = ptr;	//5步
		swap(val, ptr->val);	//6步
		ptr->height = max(ptr->left->height, ptr->right->height) + 1;	//调整右子树的高度
		height = max(left->height, right->height) + 1;	//调整根结点的高度
	}
}

在这里插入图片描述
当结点A的失衡是由其右儿子的右子树造成的时,可通过RR旋转(RR就是Right-Right,右儿子的右子树)调整这种失衡。RR旋转可以看作结点A逆时针旋转,因此又叫做左旋。
RR旋转与LL旋转完全对称,左变右,右变左即可。

template <typename Type> void AVLTree<Type>::RR()
{
	if (!empty())
	{
		AVLTree* ptr = right;
		right = ptr->right;
		ptr->right = ptr->left;
		ptr->left = left;
		left = ptr;
		swap(val, ptr->val);
		ptr->height = max(ptr->left->height, ptr->right->height) + 1;
		height = max(left->height, right->height) + 1;
	}
}

LR与RL

类似地,当结点A的失衡是由其左儿子的右子树造成的时,可通过LR旋转(Left-Right,左儿子的右子树)调整调整这种失衡。LR是LL与RR的复合旋转,先对左儿子进行RR旋转,在对根结点进行LL旋转。
在这里插入图片描述

template <typename Type> void AVLTree<Type>::LR()
{
	left->RR();	//先对左儿子RR旋转
	LL();	//再对根结点LL旋转
}

当结点A的失衡是由其右儿子的左子树造成的时,可通过RL旋转(Right-Left,右儿子的左子树)调整调整这种失衡。RL也是LL与RR的复合旋转,先对右儿子进行LL旋转,在对根结点进行RR旋转。
在这里插入图片描述

template <typename Type> void AVLTree<Type>::RL()
{
	right->LL();	//先对右儿子LL旋转
	RR();	//再对根结点RR旋转
}

插入

AVL树的插入过程与二叉搜索树基本一样,但插入操作可能会改变树的平衡状态,平衡状态通过平衡因子判定,本文的AVL树保存了树高,可以利用树高计算平衡因子,间接地判定树的平衡状态。因此与二叉搜索树不同的就是,AVL树进行插入操作时,要维护树高,还要调整失衡状态。
具体思路如下:
我们利用递归算法插入数据,并在递归返回时维护树高。
当递归返回时,左右子树的树高已经维护完毕了,所以只需要判定当前树的树高是否需要更新。当前树高等于左右子树最大值加1,即height = max(lHeight, rHeight) + 1,本文中用的是一个在插入情况下的等价写法,至于这个写法为什么等价,很好理解,这里就不赘述了。
在树高维护完毕后,我们就要利用树高来判定当前状态是否失衡,即计算平衡因子,如果失衡,就进行相应的旋转。
具体进行哪种旋转,可以根据插入操作判定。
在这里插入图片描述
如果失衡是由于向1号位插入数据引起的,那么显然应该进行LL旋转,
类似地,2号位应该进行LR旋转,3号位RL旋转,4号位RR旋转

template <typename Type> bool AVLTree<Type>::Insert(Type& val)
{
	bool ret = false;	//返回值,返回为false表示插入失败,true表示插入成功
	int lHeight, rHeight;
	if (empty())	//如果当前树为空树,说明到达了目标插入结点
	{
		this->val = val;	//将目标值val插入树中
		left = new AVLTree();	//将该结点左子树设为空树
		right = new AVLTree();	//将该结点右子树设为空树
		height = 1;	//设置树高为1
		return true;	//返回true,表示插入成功
	}
	else if (val < this->val) ret = left->Insert(val);	//如果目标值val小于当前结点值,说明应该向左子树中插入val
	else if (val > this->val) ret = right->Insert(val);	//如果目标值val大于当前结点值,说明应该向右子树中插入val
	lHeight = left->height;
	rHeight = right->height;
	if (lHeight == height || rHeight == height) height++;	//维护树高
	if (lHeight - rHeight > 1)	//平衡因子等于2,失衡
		if (val < left->val) LL();	//1号位
		else LR();	//2号位
	if (rHeight - lHeight > 1)	//平衡因子等于-2,失衡
		if (val > right->val) RR();	//4号位
		else RL();	//3号位
	return ret;
}

删除

删除操作与插入操作前半部分类似,都是利用递归算法确定目标结点。
在找到节点后就要进行删除操作,这时要分四种情况讨论:

  1. 目标结点是叶结点,即左右子树都为空,直接删除该结点即可。
  2. 目标结点左子树为空,右子树非空,用右子树替换目标结点。
  3. 目标结点右子树为空,左子树非空,用左子树替换目标结点。
  4. 目标结点左右子树都非空,用右子树中最小的结点(或左子树中最大的结点)替换目标结点。

删除完毕后要进行树高度的调整,也是用了一种等价算法。高度调整完毕后,要判定平衡状态,并进行相应的旋转,策略和插入相似,只不过我们无法通过删除操作确定旋转类型,只能通过比较树的高度来确定旋转类型。
如果平衡因子等于2,那说明失衡因素在左子树,如果左子树左儿子的高度大于等于右儿子,那么说明左子树的左儿子是失衡因素,应该进行LL旋转,反之,说明左子树的右儿子是失衡因素,应该进行LR旋转。
如果平衡因子等于-2,那说明失衡因素在右子树,如果右子树右儿子的高度大于等于左儿子,那么说明右子树的右儿子是失衡因素,应该进行RR旋转,反之,说明右子树的左儿子是失衡因素,应该进行RL旋转。

template <typename Type> bool AVLTree<Type>::Delete(Type& val)
{
	bool ret = false;	//返回值,返回false表示删除失败,返回true表示删除成功
	int lHeight, rHeight;
	AVLTree* ptr;
	if (empty()) return false;	//遇到空树,说明删除失败
	else if (val < this->val) ret = left->Delete(val);	//目标值小于当前结点值,说明目标结点在左子树
	else if (val > this->val) ret = right->Delete(val);	//目标值大于当前结点值,说明目标结点在右子树
	else	//目标值等于当前结点值,说明当前节点就是目标结点,开始进行删除操作
		if (!left->empty() && !right->empty()) this->val = right->takeOutMin();	//如果左右子树都不为空,取出右子树中的最小值,赋值给当前结点
		else	//下面的三种情况是删除后直接返回的,不用判断平衡因子
		{
			if (left->empty() && right->empty())	//左右子树都为空,直接删除
			{
				delete left;	//释放左子树
				delete right;	//释放右子树
				height = 0;	//令当前子树为空
				memset(&this->val, 0, sizeof(Type));	//清空val值
			}
			else if (left->empty())	//只有左子树为空,用右子树替换目标结点
			{
				delete left;	//释放左子树
				ptr = right;	//保存右子树
				//将原右子树的成员全部复制到当前节点
				this->val = ptr->val;
				left = ptr->left;
				right = ptr->right;
				height--;	//树高减一
			}
			else	//只有右子树为空,与只有左子树为空的情况完全对称
			{
				delete right;
				ptr = left;
				this->val = ptr->val;
				left = ptr->left;
				right = ptr->right;
				height--;
			}
			return true;	//删除成功,返回true
		}
	lHeight = left->height;
	rHeight = right->height;
	if (height > lHeight + 1 && height > rHeight + 1) height--;	//调整高度
	if (lHeight - rHeight > 1)	//平衡因子等于2
		if (left->left->height >= left->right->height) LL();	//左子树左儿子的高度大于等于左子树右儿子的高度,进行LL旋转
		else LR();	//反之,进行LR旋转
	if (rHeight - lHeight > 1)	//平衡因子等于-2
		if (right->right->height >= right->left->height) RR();	//右子树右儿子的高度大于等于右子树左儿子的高度,进行RR旋转
		else RL();	//反之,进行RL旋转
	return ret;
}

在删除函数中我们利用takeOutMin函数取出最小值。一棵AVL树的最小值一定在最左侧的结点中,因此takeOutMin函数的思路就是不断向左递推,直到遇到失败结点。同时takeOutMin函数在取出最小值后,还要将其从子树中删除,下面是其具体代码:

template <typename Type> Type AVLTree<Type>::takeOutMin()
{
	Type ret;
	int lHeight, rHeight;
	memset(&ret, 0, sizeof(Type));
	AVLTree* ptr;
	if (!empty())
		if (left->empty())	//左子树为空,即遇到失败结点,那么当前结点就是最小值
		{
			ret = val;	//取出最小值
			//删除该节点,由于左子树为空所以操作与删除操作中第二类情况相同
			delete left;
			ptr = right;
			left = ptr->left;
			val = ptr->val;
			right = ptr->right;
			height--;	//树高减一
		}
		else	//左子树不为空,就一直向左递推
		{
			ret = left->takeOutMin();
			//由于删除了一个结点,递归返回后和删除操作相同
			lHeight = left->height;
			rHeight = right->height;
			if (height > lHeight + 1 && height > rHeight + 1) height--;
			if (lHeight - rHeight > 1)
				if (left->left->height >= left->right->height) LL();
				else LR();
			if (rHeight - lHeight > 1)
				if (right->right->height >= right->left->height) RR();
				else RL();
		}
	return ret;
}

查找

AVL树的查找与二叉搜索树的查找一模一样。
本文的AVL树利用树高为0的结点来表示空树,我们将这类结点称为失败结点。当递归查找函数遇到失败结点时,就证明AVL树中没有相应的数据,查找失败。

template <typename Type> AVLTree<Type>* AVLTree<Type>::Find(Type& val)
{
	if (empty()) return nullptr;	//遇到失败结点,查找失败,返回空指针
	if (val == this->val) return this;	//当前结点值等于查找值,查找成功,返回当前结点指针
	else if (val < this->val) return left->Find(val);	//当查找值小于当前结点时,在左子树中继续查找
	else return right->Find(val);	//当查找值小于当前结点时,在右子树中继续查找
}

代码实现

#include <queue>
#include <iostream>
using namespace std;
template <typename Type> class AVLTree
{
public:
	AVLTree();
	AVLTree(Type val);
	~AVLTree();
	bool empty();
	void clear();
	AVLTree* Find(Type& val);
	bool Insert(Type& val);
	bool Delete(Type& val);
private:
	Type val;
	AVLTree* left;
	AVLTree* right;
	int height;
	Type takeOutMin();
	void LL();
	void RR();
	void LR();
	void RL();
};

template <typename Type> AVLTree<Type>::AVLTree()
{
	left = right = nullptr;
	height = 0;
	memset(&val, 0, sizeof(Type));
}

template <typename Type> AVLTree<Type>::AVLTree(Type val)
{
	left = new AVLTree();
	right = new AVLTree();
	height = 1;
	this->val = val;
}

template <typename Type> AVLTree<Type>::~AVLTree()
{
	if (!empty())
	{
		right->clear();
		left->clear();
	}
}

template <typename Type> bool AVLTree<Type>::empty()
{
	return height == 0;
}

template <typename Type> void AVLTree<Type>::clear()
{
	if (!empty())
	{
		right->clear();
		left->clear();
	}
	height = 0;
	delete this;
}

template <typename Type> AVLTree<Type>* AVLTree<Type>::Find(Type& val)
{
	if (empty()) return nullptr;
	if (val == this->val) return this;
	else if (val < this->val) return left->Find(val);
	else return right->Find(val);
}

template <typename Type> bool AVLTree<Type>::Insert(Type& val)
{
	bool ret = false;
	int lHeight, rHeight;
	if (empty())
	{
		this->val = val;
		left = new AVLTree();
		right = new AVLTree();
		height = 1;
		return true;
	}
	else if (val < this->val) ret = left->Insert(val);
	else if (val > this->val) ret = right->Insert(val);
	lHeight = left->height;
	rHeight = right->height;
	if (lHeight == height || rHeight == height) height++;
	if (lHeight - rHeight > 1)
		if (val < left->val) LL();
		else LR();
	if (rHeight - lHeight > 1)
		if (val > right->val) RR();
		else RL();
	return ret;
}

template <typename Type> bool AVLTree<Type>::Delete(Type& val)
{
	bool ret = false;
	int lHeight, rHeight;
	AVLTree* ptr;
	if (empty()) return false;
	else if (val < this->val) ret = left->Delete(val);
	else if (val > this->val) ret = right->Delete(val);
	else 
		if (!left->empty() && !right->empty()) this->val = right->takeOutMin();
		else
		{
			if (left->empty() && right->empty())
			{
				delete left;
				delete right;
				height = 0;
				memset(&this->val, 0, sizeof(Type));
			}
			else if (left->empty())
			{
				delete left;
				ptr = right;
				this->val = ptr->val;
				left = ptr->left;
				right = ptr->right;
				height--;
			}
			else
			{
				delete right;
				ptr = left;
				this->val = ptr->val;
				left = ptr->left;
				right = ptr->right;
				height--;
			}
			return true;
		}
	lHeight = left->height;
	rHeight = right->height;
	if (height > lHeight + 1 && height > rHeight + 1) height--;
	if (lHeight - rHeight > 1)
		if (left->left->height >= left->right->height) LL();
		else LR();
	if (rHeight - lHeight > 1)
		if (right->right->height >= right->left->height) RR();
		else RL();
	return ret;
}

template <typename Type> Type AVLTree<Type>::takeOutMin()
{
	Type ret;
	int lHeight, rHeight;
	memset(&ret, 0, sizeof(Type));
	AVLTree* ptr;
	if (!empty())
		if (left->empty())
		{
			ret = val;
			delete left;
			ptr = right;
			left = ptr->left;
			val = ptr->val;
			right = ptr->right;
			height--;
		}
		else
		{
			ret = left->takeOutMin();
			lHeight = left->height;
			rHeight = right->height;
			if (height > lHeight + 1 && height > rHeight + 1) height--;
			if (lHeight - rHeight > 1)
				if (left->left->height >= left->right->height) LL();
				else LR();
			if (rHeight - lHeight > 1)
				if (right->right->height >= right->left->height) RR();
				else RL();
		}
	return ret;
}

template <typename Type> void AVLTree<Type>::LL()
{
	if (!empty())
	{
		AVLTree* ptr = left;
		left = ptr->left;
		ptr->left = ptr->right;
		ptr->right = right;
		ptr->height = max(ptr->left->height, ptr->right->height) + 1;
		right = ptr;
		height = max(left->height, right->height) + 1;
		swap(val, ptr->val);
	}
}

template <typename Type> void AVLTree<Type>::RR()
{
	if (!empty())
	{
		AVLTree* ptr = right;
		right = ptr->right;
		ptr->right = ptr->left;
		ptr->left = left;
		ptr->height = max(ptr->left->height, ptr->right->height) + 1;
		left = ptr;
		height = max(left->height, right->height) + 1;
		swap(val, ptr->val);
	}
}

template <typename Type> void AVLTree<Type>::LR()
{
	left->RR();
	LL();
}

template <typename Type> void AVLTree<Type>::RL()
{
	right->LL();
	RR();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值