【C++】AVL树

一、AVL树的概念

map和set的底层是二叉搜索树,如果一棵树插入的元素接近有序,那么树会退化为单支树,在查找的时间复杂度会为O(N) ,因此对普通二叉树进行了平衡处理,即采用平衡树来实现。

两位俄罗斯的数学家 G.M.Adelson-Velskii 和E.M.Landis 1962 年 发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右 子树高度之差的绝对值不超过 1( 需要对树中的结点进行调整 ) ,即可 降低 树的高度,从而减少平均 搜索长度
AVL树的性质
1.它的左右子树都是AVL树
2.任何一颗左右子树的高度差绝对值不超过1
【C++】AVL树_第1张图片
是AVL树,子树的高度差不超过1
【C++】AVL树_第2张图片
不是AVL树,左右子树高度差超过2

二、AVL树的模拟实现

1).树结点

AVL的结点是一个KV 模型,模板参数有俩个,放置在pair里面,本文简化kv模型为k 
树的节点为AVL三叉链结构,左孩子、右孩子、父亲

平衡因子:控制节点的高度

template
struct AVLTreeNode
{
	AVLTreeNode(const T& data = T())
		: _pLeft(nullptr)
		, _pRight(nullptr)
		, _pParent(nullptr)
		, _data(data)
		, _bf(0)
	{}

	AVLTreeNode* _pLeft;
	AVLTreeNode* _pRight;
	AVLTreeNode* _pParent;
	T _data;
	int _bf;   // 节点的平衡因子
};

2).AVL树的插入

树的插入分为这几步:
1.树为空,链接根节点,返回

2.树不为空,找到插入位置,插入位置必为叶子节点

3.对孩子和父亲进行双向链接

4.修改平衡因子

--  左插入 bf--

--  右插入 bf++

5.如果插入节点影响祖先节点的平衡因子,向上调整平衡因子

--如果平衡因子为0 不做调整

--平衡因子为1 ,继续往上调整祖先

--平很因子为2,旋转调整子树

【C++】AVL树_第3张图片

新增节点导致节点的平衡因子为1 继续往上调整父亲的平衡因子

新增节点导致节点的平衡因子为2 子树作旋转调整

6.对不符合规则的平衡因子树旋转规则

1).parent的平衡因子为2 ,cur的平衡因子为1,进行左单旋。

2).parent的平衡因子为-2,cur的平衡因子为-1,进行右单旋。

3).parent的平衡因子为2,cur的平衡因子为-1,进行右左双旋。

4)parent的平衡因子为-2,cur的平衡因子为-,进行左右双旋。

	// 在AVL树中插入值为data的节点
	bool Insert(const T& data)
	{
		if (_pRoot == nullptr)
		{
			_pRoot = new Node(data);
			return true;
		}

		Node* cur = _pRoot;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_data < data)
			{
				parent = cur;
				cur = cur->_pRight;
			}
			else if (cur->_data > data)
			{
				parent = cur;
				cur = cur->_pLeft;
			}
			else
			{
				return false;
			}
		}
		//找到插入点
		cur = new Node(data);
		if (parent->_data < data)
			parent->_pRight = cur;
		else
			parent->_pLeft = cur;

		cur->_pParent = parent;

		//调整平衡因子
		while (parent)
		{
			//左减右加
			if (cur == parent->_pLeft) parent->_bf--;

			else  parent->_bf++;

			if (parent->_bf == 0) //加入前bf -1 \ 1
				break;

			else if (parent->_bf == 1 || parent->_bf == -1)  //bf==0 ,继续向上调整
			{
				cur = cur->_pParent;
				parent = parent->_pParent;
			}

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

			else
					assert(false);

			}
	}

三、AVL树的旋转

1).左单旋

插入使子树parent的平衡因子为2 ,cur的平衡因子为1 ,呈现一条直线

例如

【C++】AVL树_第4张图片

下面展示抽象图

【C++】AVL树_第5张图片

小长条表示节点高度h,h可以是0 、1的任意一种,旋转思路是把subR推上去作根,subRL是subR的左子树 

1).让subRL作parent的右子树,如果subRL不为空,subRL的父亲链接parent 

2).将parent链接subR的左 ,parent的parent链接subR (双向链接)

3).如果subR不为根,提前保存parent的parent ,链接subR的parent

4).调整平衡因子,全为0

 代码

	// 左单旋
	void RotateL(Node* pParent)
	{
		Node* subR = pParent->_pRight;
		Node* subRL = subR->_pLeft;
		Node* parentparent = pParent->_pParent;
		
		//链接subrl
		pParent->_pRight = subRL;
		if (subRL)
			subRL->_pParent = pParent;

		subR->_pLeft = pParent;
		pParent->_pParent = subR;

		if (pParent == _pRoot)
		{
			_pRoot = subR;
			_pRoot->_pParent = nullptr;
		}
		else
		{
			//判断parentparent的孩子
			if (parentparent->_pLeft == pParent)
				parentparent->_pLeft = subR;

			else
				parentparent->_pRight = subR;
			
			subR->_pParent = parentparent;
		}
		//更新平衡因子
		pParent->_bf = subR->_bf = 0;

	}

2).右单旋

右单旋的思路与做单旋基本一致,针对parent为-2,cur为-1,左边高的一条直线

抽象图:

【C++】AVL树_第6张图片

关于右单旋,需要完成四步:

1).parent的左链接subLR 如果subLR不为空,它的parent链接parent

2).parent作为subL的右子树

3).如果parent不为根,subL链接parent

4).更新平衡因子 平衡因子全为0

代码  

	// 右单旋
	void RotateR(Node* pParent)
	{
		Node* subL = pParent->_pLeft;
		Node* subLR = subL->_pRight;
		Node* parentparent = pParent->_pParent;

		pParent->_pLeft = subLR;
		if (subLR)
			subLR->_pParent = pParent;

		subL->_pRight = pParent;
		pParent->_pParent = subL;

		if (pParent == _pRoot)
		{
			_pRoot = subL;
			subL->_pParent = nullptr;
		}
		else
		{
			if (parentparent->_pLeft == pParent)
				parentparent->_pLeft = subL;
			else
				parentparent->_pRight = subL;

			subL->_pParent = parentparent;
		}
		//更新平衡因子
		subL->_bf = pParent->_bf = 0;
	}

3).左右双旋

parent为-2 subR为1 的类似折线型 要进行左右双旋

下面详细画图展示:

【C++】AVL树_第7张图片

调整的步骤如下

1.对subL节点进行左单旋

2.再对parent节点进行右单旋

3.更新平衡因子,由于新增节点可以在左子树,也可以在右子树,所以进行详细讨论

-------1.subLR的平衡因子为-1时,

【C++】AVL树_第8张图片

subLR和subL 的平衡因子变为0,parent的平衡因子为1

------2.subLR的平衡因子为1

【C++】AVL树_第9张图片

subLR和parent的平衡因子为0 ,subL的平衡因子为-1

-----3.subLR的平衡因子为0

【C++】AVL树_第10张图片

subLR的平衡因子为0时,进行双旋后,subL、subLR、parent的平衡因子都为0

	// 左右双旋
	void RotateLR(Node* pParent)
	{
		Node* subL = pParent->_pLeft;
		Node* subLR = subL->_pRight;
		int bf = subLR->_bf;

		RotateL(subL);
		RotateR(pParent);

		//调整平衡因子
		if (bf == 0)
		{
			subL->_bf = subLR->_bf = pParent->_bf = 0;
		}

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

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

		else
			assert(false);

	}

4)右左双旋

针对parent 为2  subR -1 的折线形

【C++】AVL树_第11张图片

调整步骤

1.先对subR节点进行右单旋,这样就形成直线形状

2.对parent节点进行左单旋

3.调整平衡因子

调整平衡因子需要分三类:

分别是subRL为-1  1    0 

图解标注三类平衡因子的调整:

1.

【C++】AVL树_第12张图片

subRL为-1时,parent和subRL都调整0,subR调整为0

2.

【C++】AVL树_第13张图片

subRL为1时, subLR和subR 调整为0  parent调整为-1

3.

【C++】AVL树_第14张图片

subRL的平衡因子为0时,右左单旋后,subRL subR 和parent的平衡因子都调整为0

代码

	// 右左双旋
	void RotateRL(Node* pParent)
	{
		Node* subR = pParent->_pRight;
		Node* subRL = subR->_pLeft;
		int bf = subRL->_bf;
		RotateR(subR);
		RotateL(pParent);

		//更新平衡因子
		if (bf == 0)
			subR->_bf = subRL->_bf = pParent->_bf = 0;

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

四、AVL树的性能

AVL树是一个绝对平衡的二叉树,子树高度差不超过1,极大降低查找的时间复杂度,变成稳定的O(log2_N)

但是由于要维护绝对平衡,在删除时候,要进行多次的旋转调整,甚至从叶子调整到根节点。

如果要高效查找一个数据,那么可以放在AVL树中

如果一个结构要经常发生改变,AVL树就不太适用。

总结

AVL树是一种绝对平衡的二叉树,其三叉链结果是我们首次接触,单旋转,双旋思路非常巧妙。

双旋的平衡因子调整也分多种情况,在复杂的结构中,画图描述是解决的关键。

本文关于AVL的插入进行深入的探讨,作者水平有限,如有疏忽,请指出!

gitee:

链接直达:AVL树的模拟实现​​​​​​​

你可能感兴趣的:(C++,数据结构,STL,c++)