C++数据结构 —— AVL树

目录

1.AVL树介绍

2.AVL树如何进行平衡调整

2.1平衡因子

2.2AVL树的插入

2.3左单旋

2.4右单旋

2.5左右双旋

2.6右左双旋

2.8完整代码

3.测试用例

4.验证是否为AVL树

1.AVL树介绍

AVL树是map/set/multimap/multi/set等容器的一种底层结构,其本质就是一颗二叉搜索树。但因为二叉搜索树很容易退化成单支树,所以AVL树便是对二叉搜索树进行升级改造,使其平衡。

AVL进行平衡的方法为:向二叉搜索树插入节点,保证每个节点的左右子树高度差不超过1(可以包括1),如果确实达不到这种要求,就需要通过特定的算法来达到这样的要求。一颗AVL树可以是空树,也可以是具有以下性质的二叉搜索树

        1.每个节点的左右子树都是AVL树

        2.每个节点的左右子树的高度差不超过1(可以包括1)

AVL树的搜索时间复杂度可以一直维持在O(log_2N)。

2.AVL树如何进行平衡调整

2.1平衡因子

平衡因子记录当前节点的左右子树的高度差。通过维护平衡因子,在设计AVL树时,可以很方便的判断出,什么情况下该使用什么样的调整算法。我们默认认为,左右子树高度差的计算公式为:右子树高度 - 左子树高度

那么AVL树的节点可以这样定义:

template <class K>
struct AVLTreeNode
{
	AVLTreeNode<K>* _left;
	AVLTreeNode<K>* _right;
	AVLTreeNode<K>* _parent;	//指向父节点

	K _key;

	int _bf;	//平衡因子

	AVLTreeNode(const K& key)
		:_left(nullptr), _right(nullptr), _parent(nullptr),_key(key), _bf(0)
	{}
};

2.2AVL树的插入

节点进行插入的时候,我们需要维护平衡因子,还有维护指向父节点的指针。所以,我们的插入思路,可以这样写:

template<class K>
class AVLTree
{
	typedef AVLTreeNode<K> Node;
public:
	bool insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		Node* parent = nullptr;	//记录cur节点的上一个节点
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				// key和树中的某一键值相等,退出
				return false;
			}
		}

		// 找到了新节点的插入位置
		cur = new Node(key);
		
		if (key < parent->_key)
		{
			parent->_left = cur;
			cur->_parent = parent;	//注意维护指向父节点的指针
		}
		else if (key > parent->_key)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}

		// 现在需要维护平衡因子:右子树高度 - 左子树高度

		while (parent)	//parent节点不为空时,就需要对平衡因子维护
		{
			if (parent->_left == cur)	//当新增节点在parent的左边时
			{
				parent->_bf--;
			}
			else if (parent->_right == cur)	//新增节点在parent的右边时
			{
				parent->_bf++;
			}

			if (parent->_bf == 0)	//更新平衡因子后,parent的平衡因子为0,就说明左右子树高度相等
									//不需要进行处理
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)	//parent的左子树或右子树的高度增加了
															//就需要往上更新平衡因子
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)	//parent的左右子树高度差不符合要求了
															//需要根据平衡因子的关系来选用特定的算法调整
			{
				// ...调整算法
			}
			else
			{
				assert(false);	//如果发生断言错误,则说明代码设计的有问题
			}
		}

		return true;
	}
private:
	Node* _root = nullptr;
};

现在来详细解释一下更新平衡因子的算法:

 同样的如上图所示,如果键值为9的节点作为根节点,那它势必是最后更新到的节点。所以在上面的代码中使用了while(parent)语句,当parent为空时就不进入更新平衡因子。

2.3左单旋

如果在较高右子树的右侧插入一个新节点,就需要使用左单旋算法:

void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curL = cur->_left;	//cur的左树

		parent->_right = curL;	//cur的左树变成parent的右树
		if (curL)
		{
			curL->_parent = parent;
		}

		Node* oldParent = parent->_parent;	//记录parent的父节点
		parent->_parent = cur;	//cur作为parent的父节点
		cur->_left = parent;	//parent作为cur的左树

		if (oldParent == nullptr)	
		{
			_root = cur;	//直接让cur作为根节点(因为parent的旧父节点为空)
			cur->_parent = nullptr;
		}
		else
		{
			if (oldParent->_left == parent)
			{
				oldParent->_left = cur;
				cur->_parent = oldParent;
			}
			else if (oldParent->_right == parent)
			{
				oldParent->_right = cur;
				cur->_parent = oldParent;
			}
		}
		
		parent->_bf = cur->_bf = 0;	//平衡因子都置为0(推理得出结论)
	}

2.4右单旋

如果在较高左子树的左侧插入一个新节点,就需要使用右单旋算法:

void RotateR(Node* parent)
{
	Node* cur = parent->_left;
	Node* curR = cur->_right;

	parent->_left = curR;	//cur的右树作为parent的左树
	if (curR)
	{
		curR->_parent = parent;
	}

	Node* oldParent = parent->_parent;
	parent->_parent = cur;	
	cur->_right = parent;	//parent作为cur的右树

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

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

2.5左右双旋

如果在较高左子树的右侧插入一个新节点,就需要使用左右双旋算法:

void RotateLR(Node* parent)
{
	Node* cur = parent->_left;
	Node* curR = cur->_right;

	int bf = curR->_bf;	//旋转之前记录一下cur的孩子节点的平衡因子

	RotateL(cur);
	RotateR(parent);

	if (bf == 0)	//如图所示h==0时
	{
		parent->_bf = cur->_bf = curR->_bf = 0;
	}
	else if (bf == -1)	//如图所示h==1时第一种插入方式
	{
		parent->_bf = 1;
		cur->_bf = 0;
		curR->_bf = 0;
	}
	else if (bf == 1)	//如图所示h==1时第二种插入方式
	{
		parent->_bf = 0;
		cur->_bf = -1;
		curR->_bf = 0;
	}
}

2.6右左双旋

如果在较高右子树的左侧插入一个新节点,就需要使用右左双旋算法:

void RotateRL(Node* parent)
{
	Node* cur = parent->_right;
	Node* curL = cur->_left;

	int bf = curL->_bf;

	RotateR(cur);
	RotateL(parent);

	if (bf == 0)	//h==0时
	{
		parent->_bf = cur->_bf = curL->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		cur->_bf = 1;
		curL->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = -1;
		cur->_bf = 0;
		curL->_bf = 0;
	}
}

2.8完整代码

template <class K>
struct AVLTreeNode
{
	AVLTreeNode<K>* _left;
	AVLTreeNode<K>* _right;
	AVLTreeNode<K>* _parent;	//指向父节点

	K _key;

	int _bf;	//平衡因子

	AVLTreeNode(const K& key)
		:_left(nullptr), _right(nullptr), _parent(nullptr),_key(key), _bf(0)
	{}
};

template<class K>
class AVLTree
{
	typedef AVLTreeNode<K> Node;
public:
	bool insert(const K& key)
	{  
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		Node* parent = nullptr;	//记录cur节点的上一个节点
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				// key和树中的某一键值相等,退出
				return false;
			}
		}

		// 找到了新节点的插入位置
		cur = new Node(key);

		if (key < parent->_key)
		{
			parent->_left = cur;
			cur->_parent = parent;	//注意维护指向父节点的指针
		}
		else if (key > parent->_key)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}

		// 现在需要维护平衡因子:右子树高度 - 左子树高度

	while (parent)	//parent节点不为空时,就需要对平衡因子维护
	{
		if (parent->_left == cur)	//当新增节点在parent的左边时
		{
			parent->_bf--;
		}
		else if (parent->_right == cur)	//新增节点在parent的右边时
		{
			parent->_bf++;
		}

		if (parent->_bf == 0)	//更新平衡因子后,parent的平衡因子为0,就说明左右子树高度相等
								//不需要进行处理
		{
			break;
		}
		else if (parent->_bf == 1 || parent->_bf == -1)	//parent的左子树或右子树的高度增加了
														//就需要往上更新平衡因子
		{
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)	//parent的左右子树高度差不符合要求了
														//需要根据平衡因子的关系来选用特定的算法调整
		{
			// ...调整算法
			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 if (parent->_bf = 2 && cur->_bf == -1)
			{
				RotateRL(parent);
			}
			else
			{
				assert(false);
			}

			break;		//调整完即可视为插入完毕
		}
		else
		{
			assert(false);	//如果发生断言错误,则说明代码设计的有问题
		}
	}

		return true;
	}

	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curL = cur->_left;	//cur的左树

		parent->_right = curL;	//cur的左树变成parent的右树
		if (curL)
		{
			curL->_parent = parent;
		}

		Node* oldParent = parent->_parent;	//记录parent的父节点
		parent->_parent = cur;	//cur作为parent的父节点
		cur->_left = parent;	//parent作为cur的左树

		if (oldParent == nullptr)	
		{
			_root = cur;	//直接让cur作为根节点(因为parent的旧父节点为空)
			cur->_parent = nullptr;
		}
		else
		{
			if (oldParent->_left == parent)
			{
				oldParent->_left = cur;
				cur->_parent = oldParent;
			}
			else if (oldParent->_right == parent)
			{
				oldParent->_right = cur;
				cur->_parent = oldParent;
			}
		}
		
		parent->_bf = cur->_bf = 0;	//平衡因子都置为0(推理得出结论)
	}

	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curR = cur->_right;


		parent->_left = curR;	//cur的右树作为parent的左树
		if (curR)
		{
			curR->_parent = parent;
		}

		Node* oldParent = parent->_parent;
		parent->_parent = cur;	
		cur->_right = parent;	//parent作为cur的右树

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

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


	void RotateLR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curR = cur->_right;

		int bf = curR->_bf;	//旋转之前记录一下cur的孩子节点的平衡因子

		RotateL(cur);
		RotateR(parent);

		if (bf == 0)	//如图所示h==0时
		{
			parent->_bf = cur->_bf = curR->_bf = 0;
		}
		else if (bf == -1)	//如图所示h==1时第一种插入方式
		{
			parent->_bf = 1;
			cur->_bf = 0;
			curR->_bf = 0;
		}
		else if (bf == 1)	//如图所示h==1时第二种插入方式
		{
			parent->_bf = 0;
			cur->_bf = -1;
			curR->_bf = 0;
		}
	}


	void RotateRL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curL = cur->_left;

		int bf = curL->_bf;

		RotateR(cur);
		RotateL(parent);

		if (bf == 0)	//h==0时
		{
			parent->_bf = cur->_bf = curL->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			cur->_bf = 1;
			curL->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = -1;
			cur->_bf = 0;
			curL->_bf = 0;
		}
	}


	void level()
	{
		vector<vector<string>> vec;
		if (_root == nullptr)
		{
			return;
		}

		queue<Node*> q;
		q.push(_root);

		while (!q.empty())
		{
			int size = q.size();
			vector<string> tmp;
			while (size--)
			{
				Node* front = q.front();
				q.pop();

				if (front)
				{
					tmp.push_back(to_string(front->_key));
				}
				else
				{
					tmp.push_back("nullptr");
				}

				if (front)
				{
					if (front->_left)
					{
						q.push(front->_left);
					}
					else
					{
						q.push(nullptr);
					}

					if (front->_right)
					{
						q.push(front->_right);
					}
					else
					{
						q.push(nullptr);
					}
				}

			}
			vec.push_back(tmp);


		}

		for (int i = 0; i < vec.size(); i++)
		{
			for (int j = 0; j < vec[i].size(); j++)
			{
				cout << vec[i][j] << " ";
			}
			cout << endl;
		}
	}


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

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

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

		if (root->_bf != (rightHeight - leftHeight))
		{
			cout << root->_key << "平衡因子出错:" << root->_bf << endl;
			return false;
		}

		return abs(rightHeight - leftHeight) < 2
			&& isAVLTree(root->_left)
			&& isAVLTree(root->_right);
	}


	int height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}

		int lh = height(root->_left);
		int rh = height(root->_right);

		return lh > rh ? lh + 1 : rh + 1;
	}
private:
	Node* _root = nullptr;
};

3.测试用例

二叉树非线性表,所以如果代码出现问题是很难看出问题的。所以这里给出两组测试用例,以便更早的发现问题。

常规场景:

        {16,3,7,11,9,26,18,14,15}

特殊场景:

        {4,2,6,1,3,5,15,7,16,14}

可以使用中序遍历或层序遍历来观察输出结果是否符合自己的预期。

4.验证是否为AVL树

前面一直强调,AVL树的每个节点左右子树的高度差不超过1。现在只需要写一个程序判断是否符合这个条件即可。

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

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

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

	if (root->_bf != (rightHeight - leftHeight))
	{
		cout << root->_key << "平衡因子出错:" << root->_bf << endl;
		return false;
	}

	return abs(rightHeight - leftHeight) < 2
		&& isAVLTree(root->_left)
		&& isAVLTree(root->_right);
}


int height(Node* root)
{
	if (root == nullptr)
	{
		return 0;
	}

	int lh = height(root->_left);
	int rh = height(root->_right);

	return lh > rh ? lh + 1 : rh + 1;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小龙向钱进

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

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

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

打赏作者

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

抵扣说明:

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

余额充值