【数据结构】平衡二叉搜索树(AVL树)——AVL树的概念和介绍、AVL树的简单实现、AVL树的增删查改

平衡二叉搜索树(AVL树)

  为什么要引入平衡二叉搜索树?

  在之前我们学习了二叉搜索树,二叉搜索树的结构类似于一个倒置的树,而左子树的值小于根节点的值,右节点的值大于根节点的值,这种结构使得二叉搜索树在处理有序数据时非常高效。但是如果在传入的数据为有序或接近有序,二叉搜索树会退化为单支树,类似链表、此时二叉搜索树在查找、插入、删除的优异性能都消失了。

  同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述

  最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N

  最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

             

1.AVL树的概念和介绍

  对此我们引入了平衡二叉搜索树,也叫AVL树。

  AVL树是由两位俄罗斯的数学家G. M. Adelson-VelskyE. M. Landis在1962年的论文《An algorithm for the organization of information》中发明的。这是一种自平衡二叉查找树,任何节点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。

  一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  (1)它的左右子树都是AVL树

  (2)左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

在这里插入图片描述

  如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度O( l o g 2 n log_2 n log2n)

             

2.AVL树的简单实现

  和实现二叉搜索树的节点类似,只需要考虑多平衡因子和父子节点的关系即可。

  以下为AVL树节点的定义:

template<class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;   //平衡因子

	AVLTreeNode(const pair<K, V>& kv)
		: _kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};

  定义AVL树类:

template<class K,class V>
class AVLTree
{
	//便于书写Node节点
	typedef AVLTreeNode<K, V> Node;
public:
	//AVL树增删查改函数的实现
private:
	Node* _root = nullptr;
};

             

2.1AVL树的插入

  AVL树的插入操作包括插入节点和平衡调整。具体实现步骤如下:

  (1)插入节点首先,按照普通二叉搜索树的插入方法进行插入。

  (2)平衡调整插入节点后,从插入节点开始沿着通向根节点的路径向上检查所有节点,观察它们是否仍然保持平衡。如果某个节点的平衡因子绝对值大于1,就需要进行旋转操作以重新平衡这个树。旋转操作包括单旋转和双旋转。

  插入节点实现:

//AVL树插入一个节点
bool AVLInsert(const pair<K, V>& kv)
{
	//创建cur指向根节点
	Node* cur = _root;
	Node* parent = nullptr;

	//如果AVL树为空,直接返回创建的新节点
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}
	
	//如果AVL树不为空,寻找可以插入的节点
	while (cur)
	{
		//这里类似二叉搜索树的插入
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	//插入节点
	cur = new Node(kv);
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}  

	cur->_parent = parent;

	//AVL树要保持平衡,控制平衡因子为-1、0、1
	//while()
	
	return true;
}

平衡调整实现:

新节点插入之前:

  平衡因子=右子树的高度-左子树的高度,cur插入后,parent的平衡因子一定需要调整,在插入之前,parent的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:

   (1)如果cur插入到parent的左侧,左子树高+1,只需给parent的平衡因子-1即可。

   (2)如果cur插入到parent的右侧,右子树高+1,只需给parent的平衡因子+1即可。

在这里插入图片描述

while (parent)
{
	if (cur == parent->_left)//cur插入在parent左边
	{
		parent->_bf--;
	}
	else if (cur == parent->_right)//cur插入在parent右边
	{
		parent->_bf++;
	}
}

新节点插入之后:

  当cur插入以后,parent的平衡因子可能有三种情况:0,+1 \ -1, +2 \ -2

   (3)如果parent的平衡因子为0,说明插入之前parent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功且无需旋转。

   (4)如果pParent的平衡因子为+1 \ -1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此时以pParent为根的树的高度增加,需要继续向上更新,判断是否旋转。

  (5)如果pParent的平衡因子为+2 \ -2,则parent的平衡因子违反平衡树的性质,需要对其进行旋转处理。

在这里插入图片描述

在这里插入图片描述

if (parent->_bf == 0)
{
	//更新结束
	break;
}
else if (parent->_bf == 1 || parent->_bf == -1)
{
	//继续往上更新
	cur = parent;
	parent = parent->_parent;
}
else if (parent->_bf == 2 || parent->_bf == -2)
{
	//子树不平衡了,需要旋转
}
else//如果有其他情况直接报错
{
	assert(false);
}

             

2.2AVL树的旋转

  AVL的旋转分为4种情况:

  (1)左单旋转(Left Single Rotation):当新节点cur插入在较高右子树的右侧时进行左单旋转。具体步骤为将curleft变为parent的右子树,将parent节点变为cur的左子树,然后更新相关节点的指向。如果parent是根节点,那么cur将成为新的根节点。

  (2)右单旋转(Right Single Rotation):当新节点cur插入在较低左子树的左侧时进行右单旋转。具体步骤为将curright变为parent的左子树,将parent节点变为cur的右子树,然后更新相关节点的指向。

  (3)右左双旋转(Right Left Double Rotation):先进行右单旋转,再进行左单旋转。当新节点cur插入在的左子树的右侧时,先进行右单旋转,再进行左单旋转。

  (4)左右双旋转(Left Right Double Rotation):先进行左单旋转,再进行右单旋转。当新节点cur插入在的右子树的左侧时,先进行左单旋转,再进行右单旋转。

else if (parent->_bf == 2 || parent->_bf == -2)
{
	//子树不平衡了,需要旋转
	if (parent->_bf == 2 && cur->_bf == 1)//左旋
	{
		RotateL(parent);
	}
	else if (parent->_bf == -2 && cur->_bf == -1)//右旋
	{
		RotateR(parent);
	}
	else if (parent->_bf == 2 && cur->_bf == -1)//右左双旋
	{
		RotateRL(parent);
	}
	else if (parent->_bf == -2 && cur->_bf == 1)//左右双旋
	{
		RotateLR(parent);
	}

	break;
}

             

2.2.1左旋

  (1)左单旋转(Left Single Rotation):当新节点cur插入在较高右子树的右侧时进行左单旋转。具体步骤为将curleft变为parent的右子树,将parent节点变为cur的左子树,然后更新相关节点的指向。如果parent是根节点,那么cur将成为新的根节点。

在这里插入图片描述

//左旋
void RotateL(Node* parent)
{
	//创建cur节点和父节点
	Node* cur = parent->_right;
	Node* curleft = cur->_left;

	//将右子树的左节点连接在parent的右节点上
	parent->_right = curleft;//关键步骤1
	if (curleft)//如果右子树的左节点不为空,连接一下父节点
	{
		curleft->_parent = parent;
	}

	//将父节点断开连接到原来右节点的左子树上,降低二叉树高度
	cur->_left = parent;//关键步骤2

	//仍需要处理特殊情况
	//如果原父节点不为_root,保存父节点的父节点
	Node* ppnode = parent->_parent;
	
	//两个节点连接
	parent->_parent = cur;

	//如果父节点为_root,直接更新
	if (parent == _root)
	{
		_root = cur;
		cur->_parent = nullptr;
	}
	else//如果父节点不为_root,需要重新连接
	{
		if (ppnode->_left == parent)
		{
			ppnode->_left = cur;
		}
		else//判断是父父节点的左节点还是右节点
		{
			ppnode->_right = cur;

		}

		//反转将cur节点连接父节点
		cur->_parent = ppnode;
	}

	//更新平衡因子
	parent->_bf = cur->_bf = 0;
}

             

2.2.2右旋

  (2)右单旋转(Right Single Rotation):当新节点cur插入在较低左子树的左侧时进行右单旋转。具体步骤为将curright变为parent的左子树,将parent节点变为cur的右子树,然后更新相关节点的指向。

在这里插入图片描述

//右旋
	void RotateR(Node* parent)
	{
		//取子节点和子节点中的最大节点,作为父节点的左子树
		Node* cur = parent->_left;
		Node* curright = cur->_right;

		//将父节点和左子树中的最大节点连接,降低层高
		parent->_left = curright;//重要步骤1
		if (curright)
		{
			curright->_parent = parent;
		}

		//将子节点作为根,并将原来父节点连接在子节点的右节点
		cur->_right = parent;//重要步骤2

		//上面的代码基本可以完成右旋操作,但是还要考虑parent是否为_root
		Node* ppnode = parent->_parent;

		parent->_parent = cur;

		if (ppnode == nullptr)//parent为_root
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else//parent不为_root
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else//判断cur节点在原来子树的右边还是左边,并且连接
			{
				ppnode->_right = cur;
			}

			cur->_parent = ppnode;
		}

		//右旋完成,更新平衡因子
		parent->_bf = cur->_bf = 0;
	}

             

2.2.3右左双旋

  双旋源码放在全部源码中。

  (3)右左双旋转(Right Left Double Rotation):先进行右单旋转,再进行左单旋转。当新节点cur插入在的左子树的右侧时,先进行右单旋转,再进行左单旋转。

在这里插入图片描述

             

2.2.4左右双旋

  (4)左右双旋转(Left Right Double Rotation):先进行左单旋转,再进行右单旋转。当新节点cur插入在的右子树的左侧时,先进行左单旋转,再进行右单旋转。

在这里插入图片描述

             

全部源码

#pragma once

#include<assert.h>

template<class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;   //平衡因子

	AVLTreeNode(const pair<K, V>& kv)
		: _kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
	{}
};

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	//AVL树插入一个节点
	bool AVLInsert(const pair<K, V>& kv)
	{
		//创建cur指向根节点
		Node* cur = _root;
		Node* parent = nullptr;

		//如果AVL树为空,直接返回创建的新节点
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}
		
		//如果AVL树不为空,寻找可以插入的节点
		while (cur)
		{
			//这里类似二叉搜索树的插入
			if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		//插入节点
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}  

		cur->_parent = parent;

		//AVL树要保持平衡,控制平衡因子为-1、0、1
		while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else if (cur == parent->_right)
			{
				parent->_bf++;
			}
			
			if (parent->_bf == 0)
			{
				//更新结束
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//继续往上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//子树不平衡了,需要旋转
				if (parent->_bf == 2 && cur->_bf == 1)//左旋
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)//右旋
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)//右左双旋
				{
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)//左右双旋
				{
					RotateLR(parent);
				}

				break;
			}
			else
			{
				assert(false);
			}
		}

		return true;
	}

	//左旋
	void RotateL(Node* parent)
	{
		//创建cur节点和父节点
		Node* cur = parent->_right;
		Node* curleft = cur->_left;

		//将右子树的左节点连接在parent的右节点上
		parent->_right = curleft;//关键步骤1
		if (curleft)//如果右子树的左节点不为空,连接一下父节点
		{
			curleft->_parent = parent;
		}

		//将父节点断开连接到原来右节点的左子树上,降低二叉树高度
		cur->_left = parent;//关键步骤2

		//仍需要处理特殊情况
		//如果原父节点不为_root,保存父节点的父节点
		Node* ppnode = parent->_parent;
		
		//两个节点连接
		parent->_parent = cur;

		//如果父节点为_root,直接更新
		if (parent == _root)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else//如果父节点不为_root,需要重新连接
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else//判断是父父节点的左节点还是右节点
			{
				ppnode->_right = cur;

			}

			//反转将cur节点连接父节点
			cur->_parent = ppnode;
		}

		//更新平衡因子
		parent->_bf = cur->_bf = 0;
	}

	//右旋
	void RotateR(Node* parent)
	{
		//取子节点和子节点中的最大节点,作为父节点的左子树
		Node* cur = parent->_left;
		Node* curright = cur->_right;

		//将父节点和左子树中的最大节点连接,降低层高
		parent->_left = curright;//重要步骤1
		if (curright)
		{
			curright->_parent = parent;
		}

		//将子节点作为根,并将原来父节点连接在子节点的右节点
		cur->_right = parent;//重要步骤2

		//上面的代码基本可以完成右旋操作,但是还要考虑parent是否为_root
		Node* ppnode = parent->_parent;

		parent->_parent = cur;

		if (ppnode == nullptr)//parent为_root
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else//parent不为_root
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else//判断cur节点在原来子树的右边还是左边,并且连接
			{
				ppnode->_right = cur;
			}

			cur->_parent = ppnode;
		}

		//右旋完成,更新平衡因子
		parent->_bf = cur->_bf = 0;
	}

	//右左双旋
	void RotateRL(Node* parent)
	{
		//找到双旋节点
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		int bf = curleft->_bf;//记录平衡因子

		RotateR(parent->_right);//先右旋cur,让节点保持在一条直线上
		RotateL(parent);//左旋parent

		//不同情况更新不同的平衡因子
		if (bf == 0)//新增的节点就是所需要右左旋的节点
		{
			cur->_bf = 0;
			curleft->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)//新增节点的父节点平衡因子为1,新增在了左边
		{
			cur->_bf = 0;
			curleft->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)//新增节点的父节点平衡因子为-1,新增在了右边
		{
			cur->_bf = 1;
			curleft->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	//左右双旋
	void RotateLR(Node* parent)
	{
		//找到双旋节点
		Node* cur = parent->_left;
		Node* curright = cur->_right;
		int bf = curright->_bf;

		RotateL(parent->_left);//先右旋
		RotateR(parent);//再左旋

		//更新平衡因子,新增节点更新位置不同,节点的平衡因子也不同
		if (bf == 0)
		{
			cur->_bf = 0;
			curright->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			cur->_bf = -1;
			curright->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			cur->_bf = 0;
			curright->_bf = 0;
			parent->_bf = 1;
		}
	}

	//求AVL树高
	int AVLHeight()
	{
		return _AVLHeight(_root);
	}

	int _AVLHeight(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int leftHeight = _AVLHeight(root->_left);
		int rightHeight = _AVLHeight(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	//判断AVL树是否平衡
	bool AVLIsBalance()
	{
		return _AVLIsBalance(_root);
	}

	bool _AVLIsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}

		int leftHeight = _AVLHeight(root->_left);
		int rightHeight = _AVLHeight(root->_right);

		if (rightHeight - leftHeight != root->_bf)
		{
			cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
			return false;
		}

		return abs(rightHeight = leftHeight) < 2 && _AVLIsBalance(root->_left)&& _AVLIsBalance(root->_right);
	}

private:
	Node* _root = nullptr;
};
  • 22
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 40
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 40
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鳄鱼麻薯球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值