C++AVL树的实现--详细解析旋转细节

Posted 蓝乐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++AVL树的实现--详细解析旋转细节相关的知识,希望对你有一定的参考价值。

  • 我们之前所学习的二叉搜索树由于可能出现单边树的极端情况,导致效率为O(N)。因此,本文将介绍AVL树即平衡搜索二叉树,将可以有效的避免单边树的情况。

AVL树的实现

AVL树的概念

AVL树是以发现其的两位俄罗斯数学家G.M.Adelson-Velskii和E.M.Landis的首字母命名的。其实现方法为:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 它的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1/0/1)

    如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在O(log2N) ,搜索时间复杂度O( log2N)。

AVL树的定义

AVL树结点定义

这里采用三叉链结构定义AVL树结点,即定义一个结点的父亲,左孩子与右孩子。用pair对象存储key、value值,然后定义平衡因子,用于调整AVL树保证平衡稳定性。

template <class K, class V>
struct AVLTreeNode

	pair<K,V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;//平衡因子,右子树高度-左子树高度

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

AVL树的定义

template <class K, class V>
class AVLTree

public:
	typedef AVLTreeNode<K, V> Node;
	AVLTree()
		:_root(nullptr)
	
private:
	Node* _root;

AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

    1. 按照二叉搜索树的方式插入新节点
    1. 调整节点的平衡因子(更新平衡因子加旋转)
      AVL树的插入在前半部分与二叉搜索树没有区别就是先找到新结点要插入的位置。

更新平衡因子

AVL树的旋转

右单旋

当结点的平衡因子出现异常时,若左子树高度高于右子树高度,那么该结点需要进行右单旋调整。
进行右单旋的条件为:parent的bf为-2且subL的bf为-1

右单旋代码实现

在此逻辑上,可以得出右单旋的代码:

	void RotateR(Node* parent)
	
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		//改变链接关系
		parent->_left = subLR;
		subL->_right = parent;
		if (subLR)//注意subLR可能为空
			subLR->_parent = parent;
		Node* ppNode = parent->_parent;
		//parent为根
		if (parent == _root)
			_root = subL;
		else
		
			if (ppNode->_left == parent)
			
				ppNode->_left = subL;
			
			else
			
				ppNode->_right = subL;
			
		
		parent->_parent = subL;
		subL->_parent = ppNode;
		//更新平衡因子
		parent->_bf = subL->_bf = 0;
	

左单旋

与右单旋类似的,若左子树高度高于右子树高度,那么该结点需要进行左单旋调整。
进行左单旋的条件为:parent的bf为2且subL的bf为1

左单旋代码实现

	void RotateL(Node* parent)
	
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		//改变链接关系
		parent->_right = subRL;
		if (subRL)//注意subRL可能为空
			subRL->_parent = parent;
		subR->_left = parent;
		Node* ppNode = parent->_parent;
		if (parent == _root)
			_root = subR;
		else
		
			if (ppNode->_left == parent)
				ppNode->_left = subR;
			else
				ppNode->_right = subR;
		
		subR->_parent = ppNode;
		parent->_parent = subR;
		//更新平衡因子
		subR->_bf = parent->_bf = 0;
	

双旋

右左双旋

当插入结点导致AVL树出现如图所示的情况时,需要进行右左双旋。
进行右左双旋的条件为:parent的bf为-2且subL的bf为1.
右左单旋中,需要分清楚平衡因子的变化:
通过画图分析可以知道:

  • 当subLR的bf为-1时,最终的平衡因子为:subLR的bf=0,subL的bf=0,parent的bf=1。
  • 当subLR的bf为1时,最终的平衡因子为:subLR的bf=0,subL的bf=-1,parent的bf=0。
  • 当subLR的bf为0时(subLR结点为新插入结点),最终的平衡因子为:subLR的bf=0,subL的bf=0,parent的bf=0。

右左双旋代码实现

	void RotateRL(Node* parent)
	
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		int bf = subRL->_bf;//记录下subRL的bf
		//先对subR进行一次右单旋,再对parent进行一次左单旋
		RotateR(subR);
		RotateL(parent);
		//1.bf为1
		if (bf == 1)
		
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		
		else if (bf == -1)//2.bf为-1
		
			subR->_bf = 1;
			parent->_bf = 0;
			subRL->_bf = 0;
		
		else if (bf == 0)//3.bf为0,subRL为新增结点
		
			subR->_bf = parent->_bf = subRL->_bf = 0;
		
		else//其他不合理情况,报错
			assert(false);
	

左右双旋

同样的下图中的情况也不能只通过一次单旋来调整,而要进行左右双旋。
进行左右双旋的条件为:parent的bf为2且subR的bf为-1.
左右单旋中,需要分清楚平衡因子的变化:
通过画图分析可以知道:

  • 当subRL的bf为-1时,最终的平衡因子为:subRL的bf=0,subR的bf=1,parent的bf=0。
  • 当subRL的bf为1时,最终的平衡因子为:subRL的bf=0,subR的bf=0,parent的bf=-1。
  • 当subRL的bf为0时(subLR结点为新插入结点),最终的平衡因子为:subRL的bf=0,subR的bf=0,parent的bf=0。

AVL树插入的代码实现:

	bool Insert(const pair<K, V>& kv)
	
		if (_root == nullptr)
		
			_root = new Node(kv);
			return true;
		
		//找到要插入的位置
		Node* cur = _root;
		Node* parent = _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为要插入的位置
		cur = new Node(kv);
		cur->_parent = parent;
		if (parent->_kv.first > kv.first)
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_bf = 0;
		//更新平衡因子
		while (parent)
		
			if (parent->_left == cur)
				parent->_bf--;
			else
				parent->_bf++;
			if (parent->_bf == 0)//平衡因子更新后,若parent的bf为0,无需继续更新
				break;
			else if (parent->_bf == 1 || parent->_bf == -1)//若parent的bf为1/-1,则需要继续向上更新
			
				cur = parent;
				parent = parent->_parent;
			
			else if (parent->_bf == 2 || parent->_bf == -2)//若parent的bf为2/-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)
				
					//左右双旋
					RotateLR(parent);
				
				else if (parent->_bf == 2 && cur->_bf == -1)
				
					//右左双旋
					RotateRL(parent);
				
				//旋转完成,跳出循环
				break;
			
			else//若parent的bf为其他情况,说明搜索树的平衡已经破坏,报错
				assert(false);
		
		return true;
	

AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:
1.验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明为二叉搜索树

	void InOrder()
	
		_InOrder(_root);
	
private:
	void _InOrder(Node* root)
	
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_InOrder(root->_right);
	
  1. 验证其为平衡树
    AVL树的验证即检测AVL树中每个结点的平衡因子的绝对值是否小于2。
    因此,先写出AVL树的高度接口,然后通过高度接口递归判断AVL树的每个结点平衡因子是否正常即可。
	size_t Height()
	
		return _Height(_root);
	
	bool IsAVLTree()
	
		return _IsAVLTree(_root);
	
private:
	bool _IsAVLTree(Node* root)
	
		if (root == nullptr)
			return true;
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		if (rightHeight - leftHeight != root->_bf)
			cout << "平衡因子异常:" << root->_kv.first << endl;
		return abs(rightHeight - leftHeight) < 2
			&& _IsAVLTree(root->_left)
			&& _IsAVLTree(root->_right);
	
	size_t _Height(Node* root)
	
		if (root == nullptr)
			return 0;
		return _Height(root->_left) > _Height(root->_right) ? 1 + _Height(root->_left) : 1 + _Height(root->_right);
	

AVL树的性能分析

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log2(N) 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

以上是关于C++AVL树的实现--详细解析旋转细节的主要内容,如果未能解决你的问题,请参考以下文章

[C/C++]详解STL容器6--AVL树的介绍及部分模拟实现

[C/C++]详解STL容器4--AVL树的介绍及部分模拟实现

[C/C++]详解STL容器6--AVL树的介绍及部分模拟实现

AVL树的旋转与插入(C语言)

C++AVL树(四种旋转方式)

C++AVL树(四种旋转方式)