C++入门15——AVL树

在前面的文章中,我们学习了二叉搜索树,也了解了二叉搜索树的底层原理,可是思考这样一个问题:当按照有序顺序往树中插入元素时,搜索二叉树就变成了如图所示的单支树:

而这棵树一经旋转,似乎又变成链表了呀!原本希望使用二叉搜索树能够增加效率,可这样来看,二叉搜索树似乎也并不好用了。有什么办法能够解决这个问题呢?——AVL树就为解决这一问题提供了方法。 

🥇AVL树的概念

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

即AVL树要么是一棵空树,要么具有以下性质:

①:它的左右子树都是AVL树;

②:左右子树高度差(简称平衡因子)的绝对值不超过1(-1/0/1)。

🥇AVL树的实现

🥈AVL树的节点

template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;  //节点的左孩子
	AVLTreeNode<K, V>* _right; //节点的右孩子
	AVLTreeNode<K, V>* _parent;//节点的双亲
	int _bf; //节点的平衡因子
	pair<K, V> _kv;

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _bf(0)
		, _kv(kv)
	{}
};

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:

private:
	Node* _root = nullptr;
}

🥈AVL树的插入

我们知道,AVL树就是一棵特殊的二叉搜索树,不过是在二叉搜索树的基础上引入了平衡因子,所以AVL树的插入过程可以分为两步:

1.按照二叉搜索树的方式插入新节点;

2.调整平衡因子。

 插入新节点很容易,我们直接参照数据结构9——二叉搜索树中的代码即可,可是如何调整平衡因子呢?

①:根据上图的列举的AVL树,根据图中的平衡因子计算规则,我们可以规定:如果新插入的节点是左孩子,那么平衡因子_bf--,如果新插入的节点是右孩子,那么平衡因子_bf++;

 ②:还需要注意的是,新插入孩子节点之后,其不单单会影响父亲的平衡因子,还会影响部分祖先的平衡因子,此时就要分情况讨论:有可能不会影响祖先的平衡因子,此时就无需再加额外操作(如下图1所示);也有可能会造成祖先的平衡因子的绝对值大于1,此时就要进行旋转处理(如下图2所示)。

具体代码如下:

bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < 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 (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;

		while (parent)
		{
			//更新双亲的平衡因子
			if (cur == parent->left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}
			//更新后检测双亲的平衡因子

			//①更新后,parent->_bf == 0,parent所在子树高度不变,不会影响
			//祖先节点,说明更新前parent的_bf是1或-1,parent缺孩子的那边插入
			//了节点,左右均衡了,parent的高度不变,不会影响祖先,此时更新直接结束
			if (parent->_bf == 0)
			{
				break;
			}
			//②更新后,parent->_bf == 1/-1,p所在的子树高度发生变化,会影响
			//祖先节点,说明更新前parent的_bf是0,parent有一边插入了节点,
			//parent发生变化,虽然parent的_bf符合规定,可其祖先的_bf可能会不符合规定
			//所以需要继续向上检查爷爷节点、太爷爷节点、曾爷爷节点......
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//插入前双亲的平衡因子是0,插入后双亲的平衡因子为1或-1,
				//说明以双亲为根的二叉树的高度增加了一层,因此需要继续向上调整
				cur = cur->_parent;
				parent = parent->_parent;
			}
			//③检查到有祖先节点的_bf变为了2/-2,此时不符合AVL树的规定,需要进行旋转处理
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 旋转处理
			}
			else
			{
				// 插入之前AVL树就有问题
				assert(false);
			}
		}

		return true;
	}

🥈AVL树的旋转

在上面的插入过程中,有可能会造成AVL树不平衡的结果,此时就要调整树的结构,使之平衡化,根据插入节点位置的不同,AVL树的旋转可分为四种:右单旋、左单旋、先左单旋再右单旋、先右单旋再左单旋。

🥉右单旋

右单旋是指新插入节点在较高左子树的左侧。

右单旋的基本方法是(以下图树为例):将15变成30的左孩子,再将30变成20的右孩子

具体代码如下: 

//①右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;// subL: parent的左孩子
		Node* subLR = subL->_right;// subLR: parent的左孩子的右孩子(可能为空)

		//①subLR变为parent的左孩子
		parent->_left = subLR;
		//②subLR可能不存在,如果存在,则更新subLR的父亲:subLR的父亲从subL变为parent
		if (subLR)
		{
			subLR->_parent = parent;
		}
		//③parent从subL的父亲变为subL的右孩子
		subL->_right = parent;
		//④由于parent可能是棵子树,因此在更新父亲之前必须先保存parent的父亲
		Node* pparent = parent->_parent;
		//⑤更新parent的父亲:subL从parent儿子的身份变为parent的父亲
		parent->_parent = subL;
		//⑥如果parent是根节点,更新指向根节点的指针
		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			//如果parent是子树,其可能是父亲的左子树,也可能是右子树
			if (pparent->_left == parent)
			{
				pparent->_left = subL;
			}
			else
			{
				pparent->_right = subL;
			}
			//⑦更新subL的父亲:subL的父亲从parent变为pparent
			subL->_parent = pparent;
		}

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

🥉左单旋

右单旋是指新插入节点在较高右子树的右侧。

左单旋的基本方法是(以下图树为例):将35变成30的右孩子,再将30变成40的左孩子

 具体代码如下:

//②左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;// subR: parent的右孩子
		Node* subRL = subR->_left;// subRL: parent的右孩子的左孩子(可能为空)
		//①subRL变为parent的右孩子
		parent->_right = subRL;
		//②subRL可能不存在,如果存在,则更新subRL的父亲:subRL的父亲从subR变为parent
		if (subRL)
		{
			subRL->_parent = parent;
		}
		//③parent从subR的父亲变为subR的左孩子
		subR->_left = parent;
		//④由于parent可能是棵子树,因此在更新父亲之前必须先保存parent的父亲
		Node* pparent = parent->_parent;
		//⑤更新parent的父亲:subR从parent儿子的身份变为parent的父亲
		parent->_parent = subR;
		//⑥如果parent是根节点,更新指向根节点的指针
		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			//如果parent是子树,其可能是父亲的左子树,也可能是右子树
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
			}
			else
			{
				pparent->_right = subR;
			}
			//⑦更新subR的父亲:subR的父亲从parent变为pparent
			subR->_parent = pparent;
		}
		//⑧旋转结束,更新平衡因子
		parent->_bf = 0;
		subR->_bf = 0;
	}

 🥉左右双旋

左右双旋即先左单旋再右单旋,是指新插入节点在较高左子树的右侧。

左右双旋的基本方法是(以下图树为例):先以30为根进行左单旋,再以40为根进行右单旋

具体代码如下:

//③左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		//旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
		int bf = subLR->_bf;
		//①先以parent的左孩子为根进行左单旋
		RotateL(parent->_left);
		//②再以parent为根进行右单旋
		RotateR(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);
		}
	}

🥉右左双旋

右左双旋即先右单旋再左单旋,是指新插入节点在较高右子树的左侧。

右左双旋的基本方法是(以下图树为例):先以40为根进行右单旋,再以30为根进行左单旋

具体代码如下:

//④右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		//旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
		int bf = subRL->_bf;
		//①先以parent的右孩子为根进行右单旋
		RotateR(subR);
		//②再以parent为根进行左单旋
		RotateL(parent);
		//③调整平衡因子
		subRL->_bf = 0;
		if (bf == 1)
		{
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else
		{
			parent->_bf = 0;
			subR->_bf = 0;
		}
	}

此时完善Insert函数:

    //③检查到有祖先节点的_bf变为了2/-2,此时不符合AVL树的规定,需要进行旋转处理            
    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)//左右双旋
		{
			RotateLR(parent);
		}
		else//右左双旋
		{
			RotateRL(parent);
		}
        break;
	}

🥇AVL树的验证

AVL树需要满足:①二叉搜索树和②平衡性

bool _IsBalance(Node* root, int& height)
	{
		if (root == nullptr)
		{
			height = 0;
			return true;
		}

		int leftHeight = 0, rightHeight = 0;
		if (!_IsBalance(root->_left, leftHeight)|| !_IsBalance(root->_right, rightHeight))
		{
			return false;
		}

		if (abs(rightHeight - leftHeight) >= 2)
		{
			cout << root->_kv.first << "不平衡" << endl;
			return false;
		}

		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

		height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;

		return true;
	}

	bool IsBalance()
	{
		int height = 0;
		return _IsBalance(_root, height);
	}

🥇测试

void TestAVLTree()
{
	//int a[] = { 30, 23, 17, 11, 3, 20, 18, 14, 15 };
	int a[] = { 14, 21, 16, 11, 13, 5, 15, 7, 6, 14 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		if (e == 14)
		{
			int x = 0;
		}

		t.Insert(make_pair(e, e));

		//看是插入谁导致出现的问题
		cout << e << "->" << t.IsBalance() << endl;
	}

	t.InOrder();
	cout << t.IsBalance() << endl;
}

int main()
{
	TestAVLTree();
	return 0;
}

结果如图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值