AVL树

博客实现了二叉搜索树,但是二叉搜索树本身有一定的局限性,二叉搜索树在时间性能上是具有局限性的二叉搜索树的局限性

同样的数据,可以对应不同的二叉搜索树,如下:
在这里插入图片描述

二叉搜索树可能退化成链表,相应的,二叉搜索树的查找操作是和这棵树的高度相关的,而此时这颗树的高度就是这颗树的节点数n,同时二叉搜索树相应的算法全部退化成 O(n) 级别。
显然,说二叉搜索树的查找、插入、删除这三个操作都是O(lgn) 级的,只是一个大概的估算,具体要和二叉搜索树的形状相关
二叉搜索树并不能像堆那样,保证所有的操作都一定是 O(lgn) 级别的,实对于大多数情况,在正常使用中,BST的性能非常好,出现非常极端的退化情况的概率非常小。
尽管如此,还是有机会出现这样的情况,可能有人就会想,可以像快速排序那样,在初始化的时候,将数据打乱即可当然,这是一个解决方案, 不过这个解决方案的缺点在于:需要一上来就拿到所有的数据可是在有些情况下,数据是慢慢的流入到系统的。如果在这个过程中,数据是近乎有序的话,BST的效率就令人担忧了

实际上,计算机科学家也为此想出了解决方案使得二叉搜索树无法退化成链表,称这样的二叉搜索树为平衡二叉搜索树,简称AVL树

「AVL 树中任何节点的两个子树的高度最大差别为一」

平衡二叉树的性质保证了整个二叉树的高度一定是logN 级别的

AVL树的效率保证

我们知道了AVL树优于二叉搜索树的地方,但是还有一个问题,AVL树是如何调整子树,能够一直满足任何节点的两个子树的高度最大差别为一。
为了保证AVL树是平衡的,引进了平衡因子的概念,每个节点都有自己的平衡因子,且合法的平衡因子只有0,-1,1。
平衡因子的计算方法为右子树的高度减去左子树的高度

一旦平衡因子不合法,说明已经不是合格的AVL树了,但是当我们进行插入操作时,很可能会导致插入节点的父节点平衡因子不合法,因此需要对树进行旋转操作,使其合法。

AVL树的节点

在讲树的旋转操作之前,我们先对AVL树的节点进行介绍。
相对于平衡二叉树,增加了两个成员,_bf是平衡因子,_Parents是指向父节点的指针,pair<k,v>用来储存节点的值。

template<class K,class V>
struct AVLTreeNode
{
	AVLTreeNode(K key,V value)
	 :_Left(nullptr)
	 , _Right(nullptr)
	 , _Parents(nullptr)
	 , _key(key)
	 ,_value(value)
	 ,_bf(0)
	{}
public:

	AVLTreeNode<K,V>* _Left;
	AVLTreeNode<K,V>* _Right;
	AVLTreeNode<K,V>* _Parents;
	K _key;
	V _value;
	int _bf;

};

AVL树的旋转

AVL树的旋转可以说是最难的地方,但是另一方面如果攻克了AVL树的旋转,AVL树也就没有难点可言了。AVL树的平衡因子就是帮助旋转的标签。

1.左单旋

首先看最简单的情况,节点上方的值为平衡因子,节点里的为储存的值。
在这里插入图片描述
旋转的步骤为

  • parents成为cur的左孩子
  • cur成为parents的父节点
    在这里插入图片描述
    如果我们换成更加复杂的情况

在这里插入图片描述
所以我们其实可以把上面的情况抽象为下面。
在这里插入图片描述

根据上面的图,对照着理解左旋代码
在这里插入图片描述

void _RotateL(Node*  pnode)
	{
		Node* ppnode = pnode->_Parents;
		Node* Rnode = pnode->_Right;
		pnode->_Right = Rnode->_Left;//将cur的左孩子变成parents的右孩子(有点绕口)
		if (Rnode->_Left)/别忘了,父子指针。
		{
			Rnode->_Left->_Parents = pnode;
		}

		 Rnode->_Left = pnode;//parents成为cru的左孩子
		 
		 if (ppnode == nullptr)//看是不是根接节点。
		 {
			 _Proot = Rnode;
		}
		 else
		 {
			 if (ppnode->_Left == pnode)//不要忘了把parents的父节点和cur连接上
			 {
				 ppnode->_Left = Rnode;
			 }
			 else if(ppnode->_Right == pnode)
			 {
				 ppnode->_Right = Rnode;
			 }
			 Rnode->_Parents = ppnode;
		 }
		 pnode->_Parents = Rnode;
		 pnode->_bf = Rnode->_bf = 0;//把平衡因子置为0.
	}

2.右单旋

和左单旋的思路如出一辙,如果能理解左单旋,右单旋肯定不在话下,下图为右单旋的旋转情况

在这里插入图片描述

void _RotateR(Node*  pnode)
	{
		Node* ppnode = pnode->_Parents;
		Node* Lnode = pnode->_Left;
		pnode->_Left = Lnode->_Right;
		if (Lnode->_Right)
		{
			Lnode->_Right->_Parents = pnode;
		}
		Lnode->_Right = pnode;
		
		if (ppnode == nullptr)
		{
			_Proot = Lnode;
		}
		else
		{
			Lnode->_Parents = ppnode;
			if (ppnode->_Left == pnode)
			{
				ppnode->_Left = Lnode;
			}
			else if(ppnode->_Right == pnode)
			{
				ppnode->_Right= Lnode;
			}
		}
		pnode->_Parents = Lnode;
		Lnode->_bf = 0;
		pnode->_bf = 0;
	}

3.AVL树的右左双旋

听起来挺复杂的旋转,不过我们先从最简单的情况看。
当一个子树成为下面这种情况就成功触发右左双旋。
在这里插入图片描述
首先将50和60组成的树进行右旋,再将30,60,50组成的树进行左旋,得到最终的结果。

在这里插入图片描述
看完,似乎也不是很难,因此我们写下了下面的这段代码

void _RotateRL(Node*  pnode)
{
_RotateR(pnode->_Right);
_RotateL(pnode)	;
return ;
}

但是我们换一种情况来看。
在这里插入图片描述
可以看到60的平衡因子最终为1但是我们前面写的左旋右旋函数,最终将有关的节点的平衡因子都置为0,是一个bug。
还有一种情况如下。
在这里插入图片描述
这次换成了30的平衡因子变为-1.对于右左双旋的情况分析完毕,我们可以将右左双旋的情况分为三种
在这里插入图片描述
对照上面我们重新写了一份代码。


	void _RotateRL(Node*  pnode)
	{
		Node* SURL = pnode->_Right->_Left;
		int bf = SURL->_bf;//记录改变之前的平衡因子
		_RotateR(pnode->_Right);
		_RotateL(pnode);
		
		if (bf== -1)
		{
			pnode->_bf = -1;
		}
		else if (bf == 1)
		{
			pnode->_Parents->_Right->_bf = 1;
		}
	}

4.AVL树的左右双旋

和AVL树的右左双旋思路一样,分为三种情况。
在这里插入图片描述
下面是代码

void _RotateLR(Node* pnode)
	{
		Node* SULR = pnode->_Left->_Right;
		int bf = SULR->_bf;
		_RotateL(pnode->_Right);
		_RotateR(pnode);
		if (bf == -1)
		{
			pnode->_bf = -1;
		}
		else if (bf== 1)
		{
			pnode->_Parents->_Right->_bf = 1;
		}
		SULR->_bf = 0;
	}

到这里算写完了AVL树的四种旋转方式,这部分是AVL树的重点部分,也为下面的插入做铺垫。

AVL树的插入

直接拿我们的代码来进行思路刨析


	bool Insert(const K& key, const V& value)
	{
		if (_Proot == nullptr)//如果为空树
		{
			_Proot = new Node(key, value);
			return true;
		}
		Node* root = _Proot;
		
		Node* pnode = nullptr;
		while (root)
		{
			pnode = root;
			if (root->_value > value)
			{
				root = root->_Left;
			}
			else if (root->_value < value)
			{
				root = root->_Right;
			}
			else if (root->_value == value)
				return true;
		}

		root = new Node(key, value);
		if (pnode->_value > value)//改
		{
			pnode->_Left = root;
			root->_Parents = pnode;//不要忘记父子也要连上
		}
		else
		{
			pnode->_Right = root;
			root->_Parents = pnode;
		}
		
//-------------------------------------------------上面和二叉搜索树的操作没有区别
		while (pnode)
		{
			if (root == pnode->_Left)//更新平衡因子
				pnode->_bf--;
			else if (root == pnode->_Right)
				pnode->_bf++;

			if(pnode->_bf == 0)//0说明插入后平衡
				return true;

			else if (pnode->_bf == 1 || pnode->_bf == -1)//向上更新平衡因子,并判断
			{
				root = pnode;
				pnode = root->_Parents;
			}
			else if (pnode->_bf == 2 || pnode->_bf == -2)//四种情况调用不同的旋转
			{
				if (pnode->_bf == 2)
				{
					if (root->_bf == 1)
					{
						_RotateL(pnode);
					}
					else if (root->_bf == -1)
					{
						_RotateRL(pnode);
					}

				}
				else if (pnode->_bf == -2)
				{
					if (root &&root->_bf == -1)
					{
						_RotateR(pnode);
					}
					else if (pnode->_bf == 1)
					{
						_RotateLR(pnode);
					}
				}
				break;
			}

		}
		return true;
	}

根据我们的代码我们把平衡因子的更新总结一下:我们把插入的节点叫root,他的父亲叫pnode

  • 如果root插入后pnode的平衡因子变成0,说明之前parent的平衡因子不是1就是-1,相当于我们把之前左或者右的空缺补上了,对于上一层来说子树的高度没有变化,所以直接break
  • 如果root插入后pnode的平衡因子绝对值变成1,说明parent之前平衡因子为0,这时候对于parent往上的树来说他们的子树高度发生了变化,所以需要接着迭代着向上更新
  • 如果更新的过程中pnode的平衡因子绝对值变成了2,那么说明这颗树已经不满足平衡树的规则,这时候就需要发生旋转,旋转就是对照着上面的图中的那几种情况

AVL树的删除

AVL树的删除可以说比插入还要复杂,这里主要说一下思路。
在这里插入图片描述
和二叉搜索树的删除操作类似,首先找到替代的节点,左子树最大的节点,或者右子树最小的节点,替换。然后更新平衡因子。
上面删除CUR可以用node1或者node2替换,可以分为三种情况

  • CUR左树为空,右树不为空
  • CUR右树为空,左树不为空
  • CUR左右树都不为空

替换完成后要更新parents节点的平衡因子,

  • parents平衡因子为0,说明之前parents平衡因子绝对值为1,最大长度已经改变,需要不断向上递归检查上面节点的平衡因子。
  • parents平衡因子绝对值为1,说明之前parents平衡因子绝对值为0,长度没有改变,可以结束判断。

有兴趣了解的读者可以查看《算法导论》相关章节。

AVL树的验证

这里算AVL树的结束了,但是但是对于我们构建的树我们有必要进行测试,看他到底满不满足条件,用函数判断是否为平衡二叉树,再用中序遍历判断是否满足从大到小的排序顺序。

	bool _IsBlanace(Node *root)//代码就不在详细解答了,一个简单的判断问题
	{
		if (root == nullptr)
			return true;
		int leftHeight = _Height(_Proot->_Left);
		int rightHeight = _Height(_Proot->_Right);
		
		return abs(leftHeight - rightHeight) < 2 && _IsBlanace(root->_Left) && _IsBlanace(root->_Right);
	}
	int _Height(Node* root) {
		if (root == nullptr) {
			return 0;
		}
		
		int leftheight = _Height(root->_Left);
		int rightheight = _Height(root->_Right);
		int i = leftheight > rightheight ? leftheight + 1 : rightheight + 1;
		
		
		return leftheight > rightheight ? leftheight + 1 : rightheight + 1;
	}

总结

AVL树是一种高级结构,他的查询效率为O(logn),效果惊人,即使数量在亿级别的元素里查询一个特定的元素,也只需要20到30次查找。但是,没有十全十美的事情,因为频繁的插入旋转操作会大大的影响效率,因此派生出了红黑树,而红黑树的旋转可以说是占在AVL树的基础上的,(仔细想想红黑树就是长在AVL树的尸体上的,说法有点奇怪)学会红黑树一定要理解AVL树,下面是AVL树的代码,如果有什么不对的地方,请指出了十分感谢(:D)

AVL树的代码

#include <iostream>
#include <math.h>
using namespace std;

template<class K,class V>
struct AVLTreeNode
{
	AVLTreeNode(K key,V value) 
		:_Left(nullptr)
		, _Right(nullptr)
		, _Parents(nullptr)
		, _key(key)
		,_value(value)
		,_bf(0)
	{}
public:

	AVLTreeNode<K,V>* _Left;
	AVLTreeNode<K,V>* _Right;
	AVLTreeNode<K,V>* _Parents;
	K _key;
	V _value;
	int _bf;

};

template<class K, class V>
struct AVLTree
{
	typedef AVLTreeNode<K,V> Node;
public:
	AVLTree()
	{
		_Proot = nullptr;
	}

	bool Insert(const K& key, const V& value)
	{
		if (_Proot == nullptr)
		{
			_Proot = new Node(key, value);
			return true;
		}
		Node* root = _Proot;
		
		Node* pnode = nullptr;
		while (root)
		{
			pnode = root;
			if (root->_value > value)
			{
				root = root->_Left;
			}
			else if (root->_value < value)
			{
				root = root->_Right;
			}
			else if (root->_value == value)
				return true;
		}

		root = new Node(key, value);
		if (pnode->_value > value)//改
		{
			pnode->_Left = root;
			root->_Parents = pnode;//不要忘记父子也要连上
		}
		else
		{
			pnode->_Right = root;
			root->_Parents = pnode;
		}
		

		while (pnode)
		{
			if (root == pnode->_Left)
				pnode->_bf--;
			else if (root == pnode->_Right)
				pnode->_bf++;

			if(pnode->_bf == 0)
				return true;

			else if (pnode->_bf == 1 || pnode->_bf == -1)
			{
				root = pnode;
				pnode = root->_Parents;
			}
			else if (pnode->_bf == 2 || pnode->_bf == -2)
			{
				if (pnode->_bf == 2)
				{
					if (root->_bf == 1)//哪边单旋哪边变长
					{
						_RotateL(pnode);
					}
					else if (root->_bf == -1)
					{
						_RotateRL(pnode);
					}

				}
				else if (pnode->_bf == -2)
				{
					if (root &&root->_bf == -1)
					{
						_RotateR(pnode);
					}
					else if (pnode->_bf == 1)
					{
						_RotateLR(pnode);
					}
				}
				break;
			}

		}
		return true;
	}

	void _RotateL(Node*  pnode)
	{
		Node* ppnode = pnode->_Parents;
		Node* Rnode = pnode->_Right;
		pnode->_Right = Rnode->_Left;
		if (Rnode->_Left)
		{
			Rnode->_Left->_Parents = pnode;
		}

		 Rnode->_Left = pnode;
		 pnode->_Parents = Rnode;
		
		 if (ppnode == nullptr)//
		 {
			 _Proot = Rnode;
			 _Proot->_Parents = nullptr;
		}
		 else
		 {
			 if (ppnode->_Left == pnode)
			 {
				 ppnode->_Left = Rnode;
			 }
			 else if(ppnode->_Right == pnode)
			 {
				 ppnode->_Right = Rnode;
			 }
			 Rnode->_Parents = ppnode;
		 }
		 
		 pnode->_bf = Rnode->_bf = 0;
	}

	void _RotateR(Node*  pnode)
	{
		Node* ppnode = pnode->_Parents;
		Node* Lnode = pnode->_Left;
		pnode->_Left = Lnode->_Right;
		if (Lnode->_Right)
		{
			Lnode->_Right->_Parents = pnode;
		}
		Lnode->_Right = pnode;
		
		if (ppnode == nullptr)
		{
			_Proot = Lnode;
			_Proot->_Parents = nullptr;
		}
		else
		{
			Lnode->_Parents = ppnode;
			if (ppnode->_Left == pnode)
			{
				ppnode->_Left = Lnode;
			}
			else if(ppnode->_Right == pnode)
			{
				ppnode->_Right= Lnode;
			}
		}
		pnode->_Parents = Lnode;
		Lnode->_bf = 0;
		pnode->_bf = 0;
	}

	void _RotateRL(Node*  pnode)
	{
		Node* SURL = pnode->_Right->_Left;
		int bf = SURL->_bf;
		_RotateR(pnode->_Right);
		_RotateL(pnode);
		
		if (bf == -1)
		{
			pnode->_bf = -1;
		}
		else if (bf == 1)
		{
			pnode->_Parents->_Right->_bf = 1;
		}
		SURL->_bf = 0;
	}

	void _RotateLR(Node* pnode)
	{
		Node* SULR = pnode->_Left->_Right;
		int bf = SULR->_bf;
		_RotateL(pnode->_Right);
		_RotateR(pnode);
		if (bf == -1)
		{
			pnode->_bf = -1;
		}
		else if (bf== 1)
		{
			pnode->_Parents->_Right->_bf = 1;
		}
		SULR->_bf = 0;
	}
	void showtree()
	{
		Node* root = _Proot;
		_show(root);
	}

	void _show(Node* root)
	{
		if (nullptr == root)
			return;
		_show(root->_Left);
		
			cout << root->_value;
		
		_show(root->_Right);
		

	}
	bool _IsBlanace(Node *root)//代码就不在详细解答了,一个简单的判断问题
	{
		if (root == nullptr)
			return true;
		int leftHeight = _Height(_Proot->_Left);
		int rightHeight = _Height(_Proot->_Right);
		
		return abs(leftHeight - rightHeight) < 2 && _IsBlanace(root->_Left) && _IsBlanace(root->_Right);
	}
	int _Height(Node* root) {
		if (root == nullptr) {
			return 0;
		}
		
		int leftheight = _Height(root->_Left);
		int rightheight = _Height(root->_Right);
		int i = leftheight > rightheight ? leftheight + 1 : rightheight + 1;
		
		
		return leftheight > rightheight ? leftheight + 1 : rightheight + 1;
	}


public:
	Node* _Proot;
	
};


int main()
{

	AVLTree<int,int> b;
	for (int i = 0; i <5; i++)
		b.Insert(i, i);
	b.Insert(5, 5);
	b.showtree();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值