【AVL树的模拟实现】

1 AVL树的概念

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

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)
    在这里插入图片描述如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 O ( l o g 2 n ) O(log_2 n) O(log2n),搜索时间复杂度 O ( l o g 2 n ) O(log_2 n) O(log2n).

2 AVL树结点的定义

代码:

template<class K,class V>
class AVLNode
{
    
    
public:
	AVLNode<K, V>* _left;
	AVLNode<K, V>* _right;
	AVLNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;//balance factory (右边++,左边--)

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

template<class K, class V>
class AVLTree
{
    
    
	typedef AVLNode<K, V> Node;
private:
	Node* _root=nullptr;
};

这个跟我们讲解的普通搜索二叉树的思路几乎是一样的,很容易理解。


3 AVL树的插入

这是我们今天要讲解的重点,也是难点。实现AVL树的方式有很多,博主采用的是平衡因子加三叉链这种方式来实现,因为相对于其他方式这种方式的理解要稍微简单些。

首先基本的框架搭建好:

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->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
    
    
				parent = cur;
				cur = cur->_right;
			}
			else return false;
		}
		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
    
    
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
    
    
			parent->_right = cur;
			cur->_parent = parent;
		}
			return true;
	}

这个是我们讲解普通二叉搜索树玩剩下的,很好理解。
现在的关键是如何更新平衡因子?
我们不难发现:

  • 当插入在parent左边时,平衡因子–,插入在parent右边时平衡因子++;
  • 插入后parent的平衡因子为0,便不用向上更新了,如果为-1/1,便还要向上更新;
  • 当parent的平衡因子为-2/2时说明已经出问题了,我们就要使出旋转大法修正,旋转完毕后便不用向上更新了。

代码解释:

//更新平衡因子
		while (parent)
		{
    
    
			if (cur == parent->_left)
				parent->_bf--;
			else
				parent->_bf++;

			if (parent->_bf == 0)
				break;
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
    
    
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
    
    
				//已经出错了,需要旋转处理
				if (parent->_bf == -2 && cur->_bf == -1)
				{
    
    
					//右单旋
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == 1)
				{
    
    
					//左单旋
					RotateL(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
    
    
					//先右单旋,再左单旋
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
    
    
					//先左单旋,再右单旋
					RotateLR(parent);
				}
				else
				{
    
    
					assert(false);
				}
				break;//旋转完毕已经平衡了,记得break出去
			}
			else
			{
    
    
				assert(false);
			}
		}

其中出错要旋转不外乎份4种情况:左单旋,右单旋,左右双旋,右左双旋
我们一个一个来看:

3.1 右单旋

抽象图是这样的:
在这里插入图片描述看着也很好理解,这样旋转后30 60 的平衡因子都变成了0,整个树也是平衡的。
我画了一个具象图来帮助大家理解:
h==0时:
在这里插入图片描述h==1时:

在这里插入图片描述h==2时:

在这里插入图片描述代码实现:

void RotateR(Node* parent)
	{
    
    
		Node* childL = parent->_left;
		Node* childLR = childL->_right;
		Node* grand = parent->_parent;
		parent->_left = childLR;
		if (childLR)
			childLR->_parent = parent;

		childL->_right = parent;
		parent->_parent = childL;

		//别忘了,还要链接grand与childL的关系
		if (grand == nullptr)
		{
    
    
			_root = childL;
			childL->_parent = nullptr;
		}
		else
		{
    
    
			if (grand->_left == parent)
				grand->_left = childL;
			else
				grand->_right = childL;

			childL->_parent = grand;
		}

		//更新平衡因子
		parent->_bf = childL->_bf = 0;

	}

3.2 左单旋

左单旋和右单旋类似,只是换了下位置,这里我就只给出抽象图了,不画具象图:
在这里插入图片描述代码实现:

void RotateL(Node* parent)
	{
    
    
		Node* childR = parent->_right;
		Node* childRL = childR->_left;
		Node* grand = parent->_parent;

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

		childR->_left =parent ;
		parent->_parent = childR;

		if (grand == nullptr)
		{
    
    
			_root = childR;
			childR->_parent = nullptr;
		}
		else
		{
    
    
			if (grand->_left == parent)
				grand->_left = childR;
			else
				grand->_right = childR;

			childR->_parent = grand;
		}
		
		parent->_bf = childR->_bf = 0;
	}

3.3 右左双旋

像下面这种情况,我们只是用单旋是解决不了问题的,要用双旋解决:
在这里插入图片描述先以90结点右单旋转化成了我们前面讲的单旋问题,再以30左单旋即可,但是要注意分析插入后未旋转前新节点后60的平衡因子,因为60的平衡因子不同我们旋转后所更新结点的平衡因子就有所差异。注意插入后60的平衡因子可能为0.(这里建议大家画图分析)

代码实现:

void RotateRL(Node* parent)
	{
    
    
		Node* childR = parent->_right;
		Node* childRL = childR->_left;

		int bf = childRL->_bf;
		RotateR(childR);
		RotateL(parent);

		//更新平衡因子
		childRL->_bf = 0;
		if (bf == -1)
		{
    
    
			childR->_bf = 1;
			parent->_bf = 0;
		}
		else if (bf == 1)
		{
    
    
			parent->_bf = -1;
			childR->_bf = 0;
		}
		else if (bf == 0)
		{
    
    
			;
		}
		else
		{
    
    
			assert(false);
		}
	}

3.4 左右双旋

跟右左双旋类似,这里就不在多讲了:
在这里插入图片描述
代码实现:

void RotateLR(Node* parent)
	{
    
    
		Node* childL = parent->_left;
		Node* childLR = childL->_right;

		int bf = childLR->_bf;
		RotateL(childL);
		RotateR(parent);

		//更新平衡因子
		childLR->_bf = 0;
		if (bf == -1)
		{
    
    
			childL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
    
    
			parent->_bf = 0;
			childL->_bf = -1;
		}
		else if (bf == 0)
		{
    
    
			;
		}
		else
		{
    
    
			assert(false);
		}
	}

4 AVL树的验证

代码:

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

	size_t _height(Node* root)
	{
    
    
		if (root == nullptr)
			return 0;

		int leftHeight = _height(root->_left);
		int rightHeight= _height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	bool _isbalance(Node* root)
	{
    
    
		if (root == nullptr)
			return true;

		int leftHeight = _height(root->_left);
		int rightHeight = _height(root->_right);

		return (abs(leftHeight - rightHeight) <= 1) && _isbalance(root->_left) && _isbalance(root->_right);
	}

public:

	void inorder()
	{
    
    
		_inorder(_root);
	}

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

	bool isbalance()
	{
    
    
		return _isbalance(_root);
	}

平衡二叉树的验证我们很早就讲过了,所以相信大家能够很轻易看懂代码。测试时还可以根据左右子树高度差来判断平衡因子的正确性,大家可以自己下去试试。
大家下去可以用一些随机数来测测。
有需要的老铁可以去博主码云里看看:
点击这里
好了,今天的分享就到这里了,我们下期再见。


猜你喜欢

转载自blog.csdn.net/m0_68872612/article/details/130510655
今日推荐