AVL树剖析

发布于:2025-03-30 ⋅ 阅读:(18) ⋅ 点赞:(0)

目录

引入

概念

AVL树的实现

插入

找合适位置并插入

平衡因子更新

平衡因子出错

父节点为2,子节点为1

父节点为-2,子节点为-1

父节点为2,子节点为-1

父节点为-2,子节点为1

函数检查


引入

AVL树也是搜索树,其被称为二叉平衡搜索树,AVL树实际上是对二叉树搜索树的一种优化。普通二叉搜索树可能出现极端情况——一端很长,如下,这种极端情况会导致二叉搜索树退化为单枝树,搜索效率急剧下降。

二叉搜索树-CSDN博客文章浏览阅读477次,点赞19次,收藏8次。关于搜索二叉树的结构及原理的分析,对搜索二叉树的算法进行剖析,实现搜索二叉树的结构及功能,对部分函数使用递归的方法实现 https://blog.csdn.net/2401_87944878/article/details/146377883

AVL树是基于二叉搜索树出现的,所以其也满足二叉搜索树的要求:左树小于节点,右树大于节点。关于普通二叉搜索树详细介绍课移至上方链接。


概念

AVL树基于普通二叉搜索树基础上,增加了一个要求:左右子树的高度差的绝对值不超过1

左右子树的高度差又被称为平衡因子,高度差的绝对值不超过1也就意味着平衡因子只能是1,0,-1。

平衡因子的计算方法是:平衡因子=右树高度-左树高度。

下面展示的就是各个节点的平衡因子(以下两棵树非AVL树)。

 

AVL树也正是通过调节平衡因子时刻保持在正常范围内,使得树不会出现"一边长"的问题。

所以在每一次向二叉树种插入值后,多要进行平衡因子的检查,有平衡因子出错就及时进行调整。

总结AVL树的性质:1)每一个节点的左右子树是AVL树;2)左右子树的平衡因子的绝对值不超过1。


AVL树的实现

AVL树的每一个节点包含:左右子树,父节点,存储的数(数据用pair结构体存储),平衡因子。

template<class K,class V>
struct AVLTreeNode
{
	//默认构造函数
	AVLTreeNode(const pair<K,V>& kv=pair())
		:_kv(kv)
		,_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_bf(0)
	{}

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

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	//默认构造
	AVLTree()
		:_root(nullptr)
	{}

private:
	Node* _root;   //此处也可以使用缺省值代替默认构造:  Node* _root=nullptr;
};

插入实现

与普通二叉搜索树一样,AVL树的插入也是先找到合适位置插入。

插入包含3个步骤:1)找数据插入位置并将数据插入;2)向上更新平衡因子;3)当出现错误平衡因子时,对树进行调整。

找合适位置并插入

通过二叉搜索树的性质(左树小于节点,右树大于节点)找到合适位置,再将数据插入到合适位置即可。

//插入
bool Insert(const pair<K, V>& kv)
{
	Node* newnode = new Node(kv);
	if (_root == nullptr)  //空树直接进行替代
	{ 
		_root = newnode;
		return true;
	}

	//非空树先找节点插入位置
	Node* pcur = _root;   //寻找目标位置
	Node* parent = nullptr;  //保留目标位置的父节点
	while (pcur)
	{
		if (pcur->_kv.first > kv.first)
		{
			parent = pcur;
			pcur = pcur->_left;
		}
		else if (pcur->_kv.first < kv.first)
		{
			parent = pcur;
			pcur = pcur->_right;
		}
		else   //相等说明节点中已存在,不能再进行插入
		{
			return false;
		}
	}

	//跳出循环,找到目标位置
	//将新节点插入
	if (parent->_kv.first > kv.first)  //通过父节点与插入数据比较,找到新节点在左还是右
	{
		parent->_left = newnode;
	}
	else
	{
		parent->_right = newnode;
	}
    newnode->_parent = parent;

	//进行平衡因子的调整
	//......
}

平衡因子更新

更新平衡因子方法:

1)不论如何新插入的节点没有左右子树,所以其平衡因子是0;

2)对于新插入的节点只会影响其所在节点的子树高度,因此只需要向上更新节点平衡因子即可

3)根据平衡因子计算公式:平衡因子=右树高度-左树高度。当新节点插入到父节点的左树上:父节点平衡因子-1;当插入到父节点的右树上:父节点+1

4)当向上更新时,父节点平衡因子调整后是0就不需要继续向上更新了。原因:当一个节点平衡因子变成0,不论是其+1还是-1都意味着:新增节点仅仅是使得当前节点的左右树平衡了,当前树的高度并没有发生变化,不需要继续向上调整。

//进行平衡因子的调整
Node* cur = newnode;
while (parent)   //此处条件是parent不为空。到达根时,_root的父节点为空,停止调整
{ 
	if (cur == parent->_left)
	{
		parent->_bf--;
	}
	else
	{
		parent->_bf++;
	}

	//检查平衡因子
	if (parent->_bf == 1 || parent->_bf == -1)
	{
		//继续向上调整
		cur = parent;
		parent = cur->_parent;
	}
	else if (parent->_bf == 0)
	{
		return true;  //插入完成,不用继续向上调整
	}
	else if (parent->_bf == 2 || parent->_bf == -2)
	{
		//平衡因子错误,对树进行调整
		break;
	}
	else //添加额外的else保证在平衡因子不在预定范围内时即使报错。
	{
		perror("平衡因子不在范围内");  
	}
}

if (parent == nullptr)
{
	return true;
}

平衡因子出错

平衡因子出错分为4中情况,也就对应4种处理方法,依据平衡因子划分。

单旋(一侧高):1)父节点平衡因子是2,子节点平衡因子是1;  

2)父节点平衡因子是-2,子节点平衡因子是-1;

多旋:3)父节点平衡因子是2,子节点平衡因子是-1;

4)父节点平衡因子是-2,直子节点平衡因子是1;

对于平衡因子调整的方法是:

1)保持搜索树的结构;

2)变成平衡树并降低树的高度。

父节点为2,子节点为1

左旋:对于一边高的情况,才有单旋来实现。将父节点左旋,即减小了子树的高度也解决了平衡因子异常的问题。

此处需要调整的节点:parent的父节点和右节点;cur的父节点和右节点;curleft(cur的左节点)父节点。

void RotateL(Node* parent)
{
	//调整parent的父节点,右节点
	//调整cur的父节点,左节点
	//调整curleft的父节点
	//调整parent的父节点的指向
	Node* pparent = parent->_parent; 
	Node* cur = parent->_right;
	Node* curleft = cur->_left;

	cur->_parent = pparent;
    if (parent == _root)   //注意:如果parent是根,在旋转后要对根进行更新
    {
	    _root = cur;
    }
	else
    {
		if (pparent->_left == parent)
		{
			pparent->_left = cur;
		}
		else
		{
			pparent->_right = cur;
		}
	}

	parent->_right = curleft;
	parent->_parent = cur;

	if(curleft)
	    curleft->_parent = parent;;
	cur->_left = parent;
	
	//将平衡因子进行调整
	cur->_bf = parent->_bf = 0;
}

if (parent->_bf == 2 && cur->_bf == 1)
{
	//进行左旋
	RotateL(parent);
}

父节点为-2,子节点为-1

右旋:将父节点进行右旋。

//进行右旋
void RotateR(Node* parent)
{
	//调整parent的父节点,左节点
	//调整cur的父节点,右节点
	//调整curright的父节点
	//调整parent的父节点的指向
	Node* pparent = parent->_parent;
	Node* cur = parent->_left;
	Node* curright = cur->_right;

	cur->_parent = pparent;
    if (parent == _root)   //注意:如果parent是根,在旋转后要对根进行更新
    {
	    _root = cur;
    }
	if (pparent)
	{
		if (pparent->_left == parent)
		{
			pparent->_left = cur;
		}
		else
		{
			pparent->_right = cur;
		}
	}

	parent->_left = curright;
	parent->_parent = cur;

	cur->_right = parent;
	if(curright)
	    curright->_parent = parent;

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

else if (parent->_bf == -2 && cur->_bf == -1)
{
	//进行右旋
	RotateR(parent);
}

父节点为2,子节点为-1

右左双旋:对于非单侧高的情况,就不能再仅仅使用单旋来达到结果了。此处需要使用双旋来实现。

采用右左双旋:先以cur为中心进行右旋,再以parent为中心进行左旋。

注意:双选相当于将curleft变成头,将curleft的左支给parent,将cur的右支给cur;因此平衡因子调整要看curleft的平衡因子。

1)curleft是0,parent和cur都是0;

2)curleft是1,parent是-1,cur是0;

3)culeft是-1,parent是0,cur是1;

else if (parent->_bf == 2 && cur->_bf == -1)
{
	//右左双旋
	//先对cur进行右旋,再对parent进行左旋
    Node* curleft = cur->_left;
	int bf = cur->_left->_bf;

	RotateR(cur);
	RotateL(parent);

	if (bf == 0)
	{
		parent->_bf = cur->_bf = 0;
	}
	else if (bf == 1)
    {
		parent->_bf = -1;
		cur->_bf = 0;
    }
    else if (bf == -1)
    {
		parent->_bf = 0;
	    cur->_bf = 1;
    }
	else
	{
		perror("平衡因子错误");
	}
    curleft->_bf = 0;
}

父节点为-2,子节点为1

左右双旋:对cur进行左旋,对parent进行右旋。

else if (parent->_bf == -2 && cur->_bf == 1)
{
	//进行左右双旋
    Node* curright = cur->_right;
	int bf = cur->_right->_bf;
	RotateL(cur);
	RotateR(parent);

	if (bf == 0)
	{
		parent->_bf = cur->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = 0;
		cur->_bf = -1;
	}
	else if (bf == -1)
	{
		parent->_bf = 1;
		cur->_bf = 0;
	}
	else
	{
		perror("平衡因子错误");
	}
    curright->_bf = 0;
}

插入代码汇总 

//插入
bool Insert(const pair<K, V>& kv)
{
	Node* newnode = new Node(kv);
	if (_root == nullptr)  //空树直接进行替代
	{
		_root = newnode;
		return true;
	}

	//非空树先找节点插入位置
	Node* pcur = _root;   //寻找目标位置
	Node* parent = nullptr;  //保留目标位置的父节点
	while (pcur)
	{
		if (pcur->_kv.first > kv.first)
		{
			parent = pcur;
			pcur = pcur->_left;
		}
		else if (pcur->_kv.first < kv.first)
		{
			parent = pcur;
			pcur = pcur->_right;
		}
		else   //相等说明节点中已存在,不能再进行插入
		{
			return false;
		}
	}

	//跳出循环,找到目标位置
	//将新节点插入
	if (parent->_kv.first > kv.first)  //通过父节点与插入数据比较,找到新节点在左还是右
	{
		parent->_left = newnode;
	}
	else
	{
		parent->_right = newnode;
	}
	newnode->_parent = parent;

	//进行平衡因子的调整
	//进行平衡因子的调整
	Node* cur = newnode;
	while (parent)   //此处条件是parent不为空。到达根时,_root的父节点为空,停止调整
	{
		if (cur == parent->_left)
		{
			parent->_bf--;
		}
		else
		{
			parent->_bf++;
		}

		//检查平衡因子
		if (parent->_bf == 1 || parent->_bf == -1)
		{
			//继续向上调整
			cur = parent;
			parent = cur->_parent;
		}
		else if (parent->_bf == 0)
		{
			return true;  //插入完成,不用继续向上调整
		}
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			//平衡因子错误,对树进行调整
			break;
		}
		else //添加额外的else保证在平衡因子不在预定范围内时即使报错。
		{
			perror("平衡因子不在范围内");
		}
	}

	if (parent == nullptr)
	{
		return true;
	}

	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)
	{
		//右左双旋
		//先对cur进行右旋,再对parent进行左旋
		Node* curleft = cur->_left;
		int bf = cur->_left->_bf;

		RotateR(cur);
		RotateL(parent);

		if (bf == 0)
		{
			parent->_bf = cur->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = -1;
			cur->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			cur->_bf = 1;
		}
		else
		{
			perror("平衡因子错误");
		}
		curleft->_bf = 0;
	}
	else if (parent->_bf == -2 && cur->_bf == 1)
	{
		//进行左右双旋
		Node* curright = cur->_right;
		int bf = cur->_right->_bf;
		RotateL(cur);
		RotateR(parent);

		if (bf == 0)
		{
			parent->_bf = cur->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			cur->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			cur->_bf = 0;
		}
		else
		{
			perror("平衡因子错误1");
		}
		curright->_bf = 0;
	}
	return true;
}

函数检查

在写完AVL树后,需要对树进行检查,而如果通过调试窗口一次次对比效率太低了。此处可以使用函数来完成检查,AVL树的关键就是平衡因子是否正确,所以此处可以设计一个函数完成平衡因子的检测。通过得出左右子树的高度确定真实的平衡因子,再去与当前节点存储的平衡因子进行对比。

	bool Isbance()
	{
		return Isbance(_root);
	}

	int Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		int leHeight = Height(root->_left);
		int riHeight = Height(root->_right);

		if (leHeight > riHeight)
		{
			return leHeight + 1;
		}
		else
		{
			return riHeight + 1;
		}
	}

private:
	bool Isbance(Node* root)
	{
		if (root==nullptr)
	        return true;

		int leftheight = Height(root->_left);   //得到树的高度进而确定平衡因子
		int rightheight = Height(root->_right);

		if (root->_bf < -1 || root->_bf>1)
		{
			perror("failed");
		}

		if (root->_bf!=rightheight-leftheight)  //检查平衡因子是否正确
		{
			cout << root->_kv.first << "  平衡因子错误" << endl;
		}
		return Isbance(root->_left) && Isbance(root->_right);
	}