C++:AVL树(平衡二叉树)

引言:

AVL树是一种特殊的二叉搜索树,二叉搜索树虽然可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树具有如下性质:

  • 左右子树都是AVL树
  • 左右子树高度差绝对值不超过1

 一、AVL树结点的定义

 

template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data)
  : _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
, _data(data), _bf(0)
{}
AVLTreeNode<T>* _pLeft;  // 该节点的左孩子
AVLTreeNode<T>* _pRight;  // 该节点的右孩子
AVLTreeNode<T>* _pParent; // 该节点的双亲
T _data;
int _bf;          // 该节点的平衡因子
};

二、AVL树的插入

 与普通的二叉搜索树不同,AVL树在数据插入后需要维护整棵树的平衡,并且更新结点的平衡因子

AVL树的插入过程可以分为两步: 

  1. 按照二叉搜索树的方式插入新节点
  2. 调整节点的平衡因子
bool Insert(const T& data)
	{
        //如果是空树,直接插入并结束
		if (_pRoot == nullptr)
		{
			_pRoot = new Node(data);
			return true;
		}
        

        //这一块代码的目的是找到插入位置的父亲结点
		Node* parent = nullptr;
		Node* cur = _pRoot;
		while (cur)
		{
			if (data < cur->_data)
			{
				parent = cur;
				cur = cur->_pLeft;
			}
			else if (data > cur->_data)
			{
				parent = cur;
				cur = cur->_pRight;
			}
			else
			{
				return false;
			}
		}



		if (cur == parent->_pLeft)
		{
			cur = new Node(data);
			parent->_pLeft = cur;
		}
		else
		{
			cur = new Node(data);
			parent->_pRight = cur;
		}
		//从parent结点开始向上更新祖先的平衡因子
		while (parent)
		{
			if (parent->_bf == 0)
			{
				break;//插入之后,该结点平衡因子为0说明以该结点为根的子树平衡了
                      //且插入前后高度不变,所以这个结点的祖先的平衡因子不会受到影响,结束
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
                //平衡因子从0变为1或者-1,祖先可能变成2或-2,需要继续向上更新
				cur = parent;
				parent = parent->_pParent;
			}
			else
			{
                //结点的平衡因子变成了2或者-2,需要进行旋转了
                //这里开始需要对AVL树进行旋转,之后再讲
				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
				{
					RotateLR(parent);
				}
				break;
			}
		}
		return true;
	}

三、AVL树的旋转

1.右单旋

 

 

这是一幅抽象图,左子树较高且在左子树的左侧插入 ,`h>=0,此时需要进行右单旋

 我们可以看到这样旋转之后该子树依然是一个二叉搜索树,巧妙的是,根节点的平衡因子被更新  成0了,这说明:在旋转之后不用再对祖先更改平衡因子了

 

void RotateR(Node* pParent)
	{
		Node*subL = pParent->_pLeft;
		Node* subLR = subL->_pRight;
		pParent->_pLeft = subRL;
		if (subLR)
		{
			subLR->_pParent = pParent;
		}
		Node*parentParent = pParent->_pParent;
		subL->_pRight = pParent;
		if (pRoot == pParent)
		{
			pRoot = subL;
			subL->_pParent = nullptr;
		}
		else
		{
			if (parentParent->_pLeft == pParent)
			{
				parentParent->_pLeft = subL;
			}
			else
			{
				parentParent->_pRight = subL;
			}
			subL->_pParent = parentParent;
		}
		pParent->_bf = subL->_bf = 0;
	}

2.左单旋

 左单旋和右单旋类似,只是方向相反,右子树较高且在右子树的右侧插入

void RotateL(Node* pParent)
	{
		Node*subR = pParent->_pRight;
		Node* subRL = subR->_pLeft;
		pParent->_pRight = subRL;
		if (subRL)
		{
			subRL->_pParent = pParent;
		}
		Node* parentParent = pParent->_pParent;
		pParent->_pParent = subR;
		subR->_pLeft = pParent;
		if (parent == pRoot)
		{
			pRoot = subR;
			subR->_pParent = nullptr;
		}
		else
		{
			if (parentParent->_pLeft = pParent)
			{
				parentParent->_pLeft = subR;
			}
			else
			{
				parentParent->_pRight = subR;
			}
		}
		pParent->_bf = subR->_bf = 0;
	}

 

3. 左右双旋

 对于单侧插入,只需要旋转一次。但是对于如下的情况,单次旋转无法解决问题

和单旋不同,我们需要对子树再往下画一层,如图所示

这棵树的左子树较高,并且在左子树的右子树插入,导致单旋无法一次解决

旋转过程 

 step1:

step2: 

 

新插入结点无论插入在subLR的左还是右, 最终subLR平衡因子为0

 

// 左右双旋
	void RotateLR(Node* pParent)
	{
		Node* subL = pParent->_pLeft;
		Node* subLR = pParent->_pRight;
		int bf = subLR->_bf;
		RotateL(subL);
		RotateR(pParent);
		//subLR就是新插入结点
		if (bf == 0)
		{
			pParent->_bf = subL->_bf = subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			pParent->_bf = -1;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		else if(bf==-1)
		{
			pParent->_bf = 1;
			subL->_bf = 1;
			subLR = 0;
		}
	}

4.右左双旋

 与左右双旋类似

 

//右左双旋
void RotateRL(Node* pParent)
	{
		Node* subR = pParent->_pRight;
		Node* subRL = subR->_pLeft;
		int bf = subRL->_bf;
		RotateR(subR);
		RotateL(pParent);
		//subRL就是新插入结点
		if (bf == 0)
		{
			pParent->_bf = subR->_bf = subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			pParent->_bf = 0;
			subR->_bf = -1;
			subRL->_bf = 0;
		}
		else if(bf==-1)
		{
			pParent->_bf = 1;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
	}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

迷梦y

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

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

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

打赏作者

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

抵扣说明:

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

余额充值