AVL树

AVL树

AVL树的概念

        二叉搜索树随可以缩短查找效率,但是如果数据有序或者接近有序的话讲退化为单支树,查找元素相当于再顺序表中搜索元素,效率低下。因此俄罗斯的科学家发明了AVL树。

           AVL树解决上述问题的方法是:当向二叉树中插入新结点后,如果能保证每个结点的左右子树高度差的绝对值不超过1(超过则需要调整),即可降低树的高度,从而减少平衡搜索的长度。

        一颗AVL树或者是空树,或者是具有一下性质的二叉搜索树。

        1. 它的左右子树都是AVL树

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

        像上面这颗树就是一颗AVL树。

         如果一颗二叉搜索树是高度平衡的,它就是AVL树。如果它有N个节点,那么它的高度可以保持到log_2 N,搜索时间复杂度也是log_2 N

树节点的定义

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)
	{}
};

        我们把节点定义成struct,这样所有的成员都是公有的形式。与二叉搜索树不同的是,我们现在增加了一个平衡因子和一个parent的作用就是去位置左右子树的高度差,是它们的高度差不超过1。

AVL树的插入

AVL树的插入和二叉搜索树的过程过程差不多,只是插入过程中,我们需要去更新平衡因子,进而进行旋转,让整颗树都始终保持着是一颗二叉搜索树的状态。

bool Insert(const pair<k,v>& kv)
{	
	//首先判空,如果为空,则插入元素就是根节点。
	if (_root == nullptr)
	{
		_root = new node(kv);
		return true;
	}
	//1.先按照二叉搜索树的规则将节点插入到AVL树中
	//2. 在插入节点后,AVL树的平衡性会遭到破坏,此时我们需要维护
	//我们的平衡因子,并检查是否满足AVL树
	//如果cur插入后,parent的平衡因子一定需要调整,因为在插入前,pparent的
	//平衡因子一定是-1,0,1 分一下两种情况
	// 如果cur插入到parent的左侧,只需要将parent的平衡因子+1
	// 如果cur插入到parent的右侧,只需要将parent的平衡因子-1

	//此时,我们就需要去判断parent的平衡因子,看是否满足AVL树
	//1.如果parent的平衡影子为0,说明插入之前parent是+-1,插入后被调整为了0,此时满足AVL树,插入成功
	//2.如果paren的根的树高度增加,需要继续向上更新
	//3.如果parent的平衡因子为±2,则parent违反了平衡树的性质,需要对其进行旋转处理
	node* parent = nullptr;
	node* cur = _root;
	
	//和二叉搜索树一样正常插入
	while (cur)
	{
		if (kv.first > cur->_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 (kv.first < parent->_kv.first)
		parent->_left = cur;
	else
		parent->_right = cur;

	cur->_parent = parent;
	//更新平衡因子
	while (parent)
	{
		if (cur == parent->_left)
			parent->_bf--;
		else
			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)
		{
			//旋转
		}
	}
	return true;
}

AVL树的旋转

如果在一颗原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使整棵树保持平衡。根据节点插入的位置不同,AVL树分为4种:

右单旋

我们看到,上面是右单旋的图,在插入时,AVL树是平衡的,新节点插入到了30的左侧,30左子树增加了一层,导致60这棵树不平衡,要让60平衡,则需要将左边整棵树将一层,并且使整棵树还是保持为AVL数。

可以看到,我们把30的右边给给了60的左边,30的右边是比30大的,比60小的,所以我们把图中 的b 给给60的左时是没有违反平衡树的规则的,此时60再变为30的右,整颗树的旋转就完成了。

我们再来看看平衡因子,这里的整颗树就30,和60需要更新平衡因子。

解释:以上图为例,parent是60,subL是30,subLR是b

注意:如果parent是根节点的话我们就需要让subL成为根,否则的话就继续向上更新就好了。

下面的代码大家可以跟着图对比一下。

void RotatoR(node* parent)
{    
    //修改节点的指向
	node *subL = parent->_left;
	node* subLR = subL->_right;

	parent->_left = subLR;
	if (subLR)
		subLR->_parent = parent;

	node* pparent = parent->_parent;

	subL->_right = parent;
	parent->_parent = subL;
    
    //如果parent是根节点,则需要更换更节点
	if (pparent == nullptr)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
    //parent不是根节点,继续向上更新
	else
	{
		if (pparent->_left == parent)
			pparent->_left = subL;
		else
			pparent->_right = subL;
		subL->_parent = pparent;
	}
    //更新平衡因子

	subL->_bf = parent->_bf = 0;
}

左单旋

和上面的情况是步骤都是差不多的,首先我们看到60的右边插入了新节点,导致以30为根的树结构的平衡因子变为了2,不平衡了,所以需要旋转,同样的,我们把60的左给给30的右,30再作为60的左,整棵树就平衡了,最后我们再更新平衡因子。

注意:如果parent是根节点的话我们就需要让subR成为根,否则的话就继续向上更新就好了。

void RotatoL(node* parent)
{
	node* subR= parent->_right;
	node* subRL = subR->_left;

	parent->_right = subRL;
	if (subRL)
		subRL->_parent = parent;

	node* pparent = parent->_parent;

	subR->_left = parent;
	parent->_parent = subR;

	if (pparent == nullptr)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (parent == pparent->_left)
			pparent->_left = subR;
		else
			pparent->_right = subR;
		subR->_parent = pparent;
	}
	subR->_bf = parent->_bf = 0;
}

左右双旋(先左单旋,再右单旋)

看下上图,如果说我们把新插入节点插在60的左边,这样的话我们仅仅通过通过左单旋或者右单旋是搞不定的(如果说你细心的话,肯定是想到了)。那这种情况该怎么办呢?

这个时候,我们就需要进行左右双旋,首先,我们先以30为根节点,我们对30这颗子树先进行做单旋,旋转成第三幅图这样,我们再将90这棵树进行右单旋就达到了我们想要的结果。最后,我们再将平衡因子更新一下就好了。

注意:这里的平衡因子更新,需要由subLR的平衡因子来决定。如上图(subLR就是60)。

void RotatoLR(node* parent)
{
	node* subL = parent->_left;
	node* subLR = subL->_right;
	int bf = subLR->_bf;
    //分别调用我们上面写的左右双旋
	RotatoL(subL);
	RotatoR(parent);
    
    //对于不同的subLR的平衡因子,他们的更新情况也不同
	if (bf == -1)
	{
		subLR->_bf = 0;
		subL->_bf = 0;
		parent->_bf = 1;
	}
	else if (bf == 1)
	{
		subLR->_bf = 0;
		subL->_bf = -1;
		parent->_bf = 0;
	}
	else if (bf == 0)
	{
		subLR->_bf = 0;
		subL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

总结一下:我们直接看图,我们看看subLR(上图也就是60这个节点),我们直接从第一个图看到最后这个结果图,它的整个旋转过程就是把60的左子树分给30的右子树,把60的右子树分给90的左子树,再让90变为整颗树的根。

右左双旋(先右单旋,再左单旋)

因为我们上面已经说过了其实它和左右双旋是差不多的,我们先以90这个节点进行右单旋,再以30这个节点进行做单旋。

同样的,我们直接来看结果,它就是把60的左子树给给了30的右子树,60的右子树给给90 的左子树,然后30,90分别为60的左子树和右子树,60再做整颗树的根。

void RotatoRL(node* parent)
{
	node* subR = parent->_right;
	node* subRL = subR->_left;

	int bf = subRL->_bf;
	RotatoR(subR);
	RotatoL(parent);

	if (bf == 0)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == 1)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = -1;
	}
	else if (bf == -1)
	{
		subR->_bf = 1;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);
	}

}

代码实现

#pragma once
#include <iostream>
#include<assert.h>
using namespace std;

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:
	bool Insert(const pair<k,v>& kv)
	{	
		//首先判空,如果为空,则插入元素就是根节点。
		if (_root == nullptr)
		{
			_root = new node(kv);
			return true;
		}
		//1.先按照二叉搜索树的规则将节点插入到AVL树中
		//2. 在插入节点后,AVL树的平衡性会遭到破坏,此时我们需要维护
		//我们的平衡因子,并检查是否满足AVL树
		//如果cur插入后,parent的平衡因子一定需要调整,因为在插入前,pparent的
		//平衡因子一定是-1,0,1 分一下两种情况
		// 如果cur插入到parent的左侧,只需要将parent的平衡因子+1
		// 如果cur插入到parent的右侧,只需要将parent的平衡因子-1

		//此时,我们就需要去判断parent的平衡因子,看是否满足AVL树
		//1.如果parent的平衡影子为0,说明插入之前parent是+-1,插入后被调整为了0,此时满足AVL树,插入成功
		//2.如果paren的根的树高度增加,需要继续向上更新
		//3.如果parent的平衡因子为±2,则parent违反了平衡树的性质,需要对其进行旋转处理
		node* parent = nullptr;
		node* cur = _root;
		
		//和二叉搜索树一样正常插入
		while (cur)
		{
			if (kv.first > cur->_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 (kv.first < parent->_kv.first)
			parent->_left = cur;
		else
			parent->_right = cur;

		cur->_parent = parent;
		//更新平衡因子
		while (parent)
		{
			if (cur == parent->_left)
				parent->_bf--;
			else
				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 && parent->_left->_bf==-1)
				{
					//右单旋
					RotatoR(parent);
				}
				else if (parent->_bf == 2 &&parent->_right->_bf== 1)
				{
					//左单旋
					RotatoL(parent);
				}
				else if (parent->_bf == -2 && parent->_left->_bf==1)
				{
					//左右双旋
					RotatoLR(parent);
				}
				else if (parent->_bf == 2 &&  parent->_right->_bf== -1)
				{
					RotatoRL(parent);
				}
				else
				{
					assert(false);
				}
				break;
			}
			else
			{
				assert(false);
			}
		}
		return true;
	}

	void RotatoR(node* parent)
	{
		node *subL = parent->_left;
		node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		node* pparent = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (pparent == nullptr)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
			subL->_parent = pparent;
		}
		subL->_bf = parent->_bf = 0;
	}

	void RotatoL(node* parent)
	{
		node* subR= parent->_right;
		node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		node* pparent = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (pparent == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (parent == pparent->_left)
				pparent->_left = subR;
			else
				pparent->_right = subR;
			subR->_parent = pparent;
		}
		subR->_bf = parent->_bf = 0;
	}

	void RotatoLR(node* parent)
	{
		node* subL = parent->_left;
		node* subLR = subL->_right;
		int bf = subLR->_bf;
		RotatoL(subL);
		RotatoR(parent);

		if (bf == -1)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void RotatoRL(node* parent)
	{
		node* subR = parent->_right;
		node* subRL = subR->_left;

		int bf = subRL->_bf;
		RotatoR(subR);
		RotatoL(parent);

		if (bf == 0)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
			subR->_bf = 0;
			subRL->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			subR->_bf = 1;
			subRL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}

	}

	void Inorder()
	{
		_Inorder(_root);
	}

	int height()
	{
		return _height(_root);
	}

	int size()
	{
		return _size(_root);
	}

	bool isbalance()
	{
		return _isbalancetree(_root);
	}
private:
	node* _root=nullptr;

	int _size(node* _root)
	{
		if (_root == nullptr)
			return 0;

		return 1 + _size(_root->_left) + _size(_root->_right);
	}

	bool _isbalancetree(node* _root)
	{
		if (_root == nullptr)
			return true;

		int leftheight = _height(_root->_left);
		int rightheight = _height(_root->_right);
		if (abs(leftheight - rightheight) >= 2)
			return false;

		return _isbalancetree(_root->_left) && _isbalancetree(_root->_right);
	}

	int _height(node* _root)
	{
		if (_root == 0)
		{
			return 0;
		}

		int leftheight = _height(_root->_left);
		int rightheight = _height(_root->_right);

		return leftheight > rightheight ? leftheight + 1 : rightheight + 1;
	}

	void  _Inorder(node* _root)
	{
		if (_root == nullptr)
			return;
		_Inorder(_root->_left);
		cout << _root->_kv.first << ": " << _root->_kv.second << endl;
		_Inorder(_root->_right);
	}
};

我们总代码里还包含了一点测试AVL树的高度的代码,大家了解即可,能看懂就看,看不懂也没关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值