二叉平衡树的实现

目录

  • [1 二叉平衡树的概念](#1 二叉平衡树的概念)
  • [2 二叉平衡树的结构](#2 二叉平衡树的结构)
  • [3 二叉平衡树的插入](#3 二叉平衡树的插入)
    • [3.1 插入](#3.1 插入)
    • [3.2 旋转](#3.2 旋转)
      • [3.2.1 右单旋](#3.2.1 右单旋)
      • [3.2.2 左单旋](#3.2.2 左单旋)
      • [3.2.3 右左双旋](#3.2.3 右左双旋)
      • [3.2.4 左右双旋](#3.2.4 左右双旋)
  • [4 二叉平衡树的查找](#4 二叉平衡树的查找)
  • [5 二叉平衡树的代码](#5 二叉平衡树的代码)

1 二叉平衡树的概念

二叉平衡树,也叫 AVL 树,它的特性是树中的每个结点,左右子树高度差的绝对值不超过 1 ,它可以是一棵空树

在 AVL 树中,有个概念叫做平衡因子,它是一个结点左右子树高度的差,一般来说,如果一棵树是 AVL 树,那么它每个结点的平衡因子都不会超过 1,由于 AVL 树对平衡的限制,它的高度一般维持在 log2N 左右,所以它增删查的效率为 O(logN)

2 二叉平衡树的结构

二叉平衡树后续的操作需要找到父结点,所以在这里实现为三叉链的结构,加入平衡因子是为了通过平衡因子来判断是否平衡,维护二叉平衡树平衡的特性

cpp 复制代码
//AVL树的结点
template<class K, class V>
class AVLTreeNode
{
	pair<K, V> _kv; //存储的键值对
	AVLTreeNode<K, V>* _left; //指向左孩子的指针
	AVLTreeNode<K, V>* _right; //指向右孩子的指针
	AVLTreeNode<K, V>* _parent; //指向父结点的指针
	int _balanceFactor; //平衡因子

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

//AVL树本体
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode Node;
public:
	//...
private:
	Node* _root; //根节点指针
};

3 二叉平衡树的插入

3.1 插入

二叉平衡树在进行插入时,要按照 二叉排序树(BST) 的方式来进行插入,假设要插入的值为 x,当前结点的值为 val:

  • x > val,向右子树移动来寻找插入点
  • x < val,向左子树移动来寻找插入点
  • x == val,由于有重复的值,所以不进行插入

当找到空结点时,将 x 插入至对应的位置

插入完成之后,由于子树中多了一个结点,所以要对父结点平衡因子进行修改,修改时,在这里采用右子树高度 - 左子树高度的方式:

  • 左子树****中新增一个结点,左子树高度加一,右子树的高度不变,那么平衡因子就需要 减一
  • 右子树****中新增一个结点,右子树高度加一,左子树的高度不变,那么平衡因子就需要 加一

平衡因子的变化,会决定是否进行旋转,是否继续向上更新平衡因子:

  • 如果平衡因子变成了 0 ,说明原来的平衡因子是 1 或 -1 ,子树两边一高一低 ,此时插入了一个结点到左子树或右子树中,导致左子树或右子树高度加一,子树两边一样高,这个时候不会影响到更上层的父结点,所以不需要继续向上更新平衡因子


  • 如果平衡因子变成了 1 或 -1 ,说明原来平衡因子是 0,子树两边一样高 ,此时插入了一个结点到左子树或右子树中,导致左子树或右子树高度加一,子树两边一高一低,那么会影响至更上层的父结点,所以还要向上更新,直到平衡因子为 0,最坏可能需要更新至根节点


  • 如果平衡因子变成了 2 或 -2 ,说明原来的平衡因子是 1 或 -1 ,子树两边一高一低 ,此时又往高的那一侧插入了一个结点,导致它变的更高了,这时候就发生了不平衡,需要进行旋转 ,通过旋转让子树恢复平衡,由于旋转较为复杂,所以在下面另起一个章节进行说明


插入实现(无旋转):

cpp 复制代码
bool Insert(const pair<K, V>& kv)
{
	if (!_root)
	{
		_root = new Node(kv);
		return true;
	}

	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		if (kv.first > cur->_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);
	cur->_parent = parent;
	if (cur->_kv.first < parent->_kv.first) //在父结点的左子树中插入
	{
		parent->_left = cur;
	}
	else if (cur->_kv.first > parent->_kv.first) //在父结点的右子树中插入
	{
		parent->_right = cur;
	}

	while (parent)
	{
		//更新父结点的平衡因子
		if (cur == parent->_left)
		{
			parent->_balanceFactor--;
		}
		else if (cur == parent->_right)
		{
			parent->_balanceFactor++;
		}

		if (parent->_balanceFactor == 0)
		{
			break;
		}
		else if (parent->_balanceFactor == 1 || parent->_balanceFactor == -1) //平衡因子是1或-1
		{
			//继续向上更新
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_balanceFactor == 2 || parent->_balanceFactor == -2) //平衡因子是2或-2
		{
			//旋转
			//...
		}
	}

	return true;
}

3.2 旋转

如果插入后,父结点的平衡因子是 2 或 -2,此时就要进行旋转操作,旋转主要有右单旋,左单旋,右左双旋和左右双旋四种情况,但是不管使用哪种旋转的方式,最终的目的都是使树恢复平衡

3.2.1 右单旋

如果新插入的结点位于父结点左子树的左子树中,并且此时发生了不平衡,那么就说明,该子树太高了,此时要进行右单旋来调整平衡,右单旋时,结点的变化如下:


  • 旋转时,如果 parent 是整棵树的根节点,那么旋转完成后,subL 会成为整棵树的根节点
  • 旋转时,如果 parent 不是整棵树的根节点,而是当前子树的根节点,那么旋转完成后,subL 会成为新的子树的根节点,此时需要更改祖父结点 grandParent 的指向
  • 旋转时,subLR 可能为空,所以需要进行判断,subLR 不为空,才更新它的父结点

右单旋代码实现:

cpp 复制代码
//右单旋
void RotateToRight(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	Node* grandParent = parent->_parent;

	//更新子树
	parent->_left = subLR;
	subL->_right = parent;
	parent->_parent = subL;
	if (subLR)
		subLR->_parent = parent;

	if (parent == _root) //父结点是根节点,更新根节点
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else //如果父结点不是根节点,那么要更新组父结点
	{
		subL->_parent = grandParent;
		if (grandParent->_left == parent)
		{
			grandParent->_left = subL;
		}
		else if (grandParent->_right == parent)
		{
			grandParent->_right = subL;
		}
	}
	
	//旋转后一定平衡,更新平衡因子
	subL->_balanceFactor = 0;
	parent->_balanceFactor = 0;
}

3.2.2 左单旋

如果新插入的结点位于父结点右子树的右子树中,并且此时发生了不平衡,那么就说明,该子树太高了,此时要进行左单旋来调整平衡,左单旋时,结点的变化如下:


  • 旋转时,如果 parent 是整棵树的根节点,那么旋转完成后,subR 会成为整棵树的根节点
  • 旋转时,如果 parent 不是整棵树的根节点,而是当前子树的根节点,那么旋转完成后,subR 会成为新的子树的根节点,此时需要更改祖父结点 grandParent 的指向
  • 旋转时,subRL 可能为空,所以需要进行判断,subRL 不为空,才更新它的父结点

左单旋代码实现:

cpp 复制代码
	//左单旋
	void RotateToLeft(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* grandParent = parent->_parent;

		//更新子树
		subR->_left = parent;
		parent->_right = subRL;
		parent->_parent = subR;
		if (subRL)
			subRL->_parent = parent;

		if (parent == _root)  //父结点是根节点,更新根节点
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else  //如果父结点不是根节点,那么要更新组父结点
		{
			subR->_parent = grandParent;
			if (grandParent->_left == parent)
			{
				grandParent->_left = subR;
			}
			else if (grandParent->_right == parent)
			{
				grandParent->_right = subR;
			}
		}

		//旋转后一定平衡,更新平衡因子
		subR->_balanceFactor = 0;
		parent->_balanceFactor = 0;
	}

3.2.3 右左双旋

如果新插入的结点位于父结点右子树的左子树中,并且此时发生了不平衡,就要进行右左双旋来调整平衡,右左双旋时,结点和平衡因子的变化如下:



subRL 的平衡因子,说明了新结点插入到了 b 中还是 c 中,会直接影响到最后 parent,subRL,subR 的平衡因子,因为 b 和 c 这两棵子树旋转后的位置是不一样的

  • subRL 平衡因子为 -1,那么最后 parent 平衡因子为 0,subRL 平衡因子为1,subR 平衡因子为 0
  • subRL 平衡因子为 1,那么最后 parent 平衡因子为 -1,subRL 平衡因子为 0,subR 平衡因子为 0
  • subRL 平衡因子为 0,那么最后 parent 平衡因子为 0,subRL 平衡因子为 0,subR 平衡因子为 0

右左双旋代码实现:

cpp 复制代码
	//右左双旋
void RotateToRightThenToLeft(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int balanceFactor = subRL->_balanceFactor;

	RotateToRight(subR);
	RotateToLeft(parent);

	//subRL平衡因子决定最后的平衡因子
	if (balanceFactor == -1)
	{
		subRL->_balanceFactor = 0;
		parent->_balanceFactor = 0;
		subR->_balanceFactor = 1;
	}
	else if (balanceFactor == 1)
	{
		subRL->_balanceFactor = 0;
		parent->_balanceFactor = -1;
		subR->_balanceFactor = 0;
	}
	else if (balanceFactor == 0)
	{
		subRL->_balanceFactor = 0;
		parent->_balanceFactor = -1;
		subR->_balanceFactor = 0;
	}
}

3.2.4 左右双旋

如果新插入的结点位于父结点左子树的右子树中,并且此时发生了不平衡,就要进行左右双旋来调整平衡,左右双旋时,结点和平衡因子的变化如下:



subLR 的平衡因子,说明了新结点插入到了 b 中还是 c 中,会直接影响到最后 parent,subLR,subL 的平衡因子,因为 b 和 c 这两棵子树旋转后的位置是不一样的

  • subLR 平衡因子为 -1,那么最后 parent 平衡因子为 0,subLR 平衡因子为1,subL 平衡因子为 0
  • subLR 平衡因子为 1,那么最后 parent 平衡因子为 -1,subLR 平衡因子为 0,subL 平衡因子为 0
  • subLR 平衡因子为 0,那么最后 parent 平衡因子为 0,subLR 平衡因子为 0,subL 平衡因子为 0

左右双旋代码实现:

cpp 复制代码
void RotateToLeftThenToRight(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int balanceFactor = subLR->_balanceFactor;

	RotateToLeft(subL);
	RotateToRight(parent);

	if (balanceFactor == -1)
	{
		subLR->_balanceFactor = 0;
		parent->_balanceFactor = 1;
		subL->_balanceFactor = 0;
	}
	else if (balanceFactor == 1)
	{
		subLR->_balanceFactor = 0;
		parent->_balanceFactor = 0;
		subL->_balanceFactor = -1;
	}
	else if (balanceFactor == 0)
	{
		subLR->_balanceFactor = 0;
		parent->_balanceFactor = 0;
		subL->_balanceFactor = 0;
	}
	else
	{
		assert(false);
	}
}

4 二叉平衡树的查找

二叉平衡树的查找十分简单,与二叉排序树类似,只需要使用传入函数的值 key 与结点的值进行对比,在树中进行移动即可:

  • key < 结点的值,向左子树移动
  • key > 结点的值,向右子树移动
  • key == 结点的值,查找到返回 true
  • 未查找到返回 false
cpp 复制代码
bool Find(const K& key)
{
	Node* cur = _root;

	while (cur)
	{
		if (key > cur->_kv.first)
		{
			cur = cur->_right;
		}
		else if (cur->_kv.first > key)
		{
			cur = cur->_left;
		}
		else
		{
			return true;
		}
	}
	
	return false;
}

5 二叉平衡树的代码

cpp 复制代码
//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 _balanceFactor; //平衡因子

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

//AVL树本体
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	//查找
	bool Find(const K& key)
	{
		Node* cur = _root;

		while (cur)
		{
			if (key > cur->_kv.first)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return true;
			}
		}
		
		return false;
	}

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

		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (kv.first > cur->_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);
		cur->_parent = parent;
		if (cur->_kv.first < parent->_kv.first) //在父结点的左子树中插入
		{
			parent->_left = cur;
		}
		else if (cur->_kv.first > parent->_kv.first) //在父结点的右子树中插入
		{
			parent->_right = cur;
		}

		while (parent)
		{
			//更新父结点的平衡因子
			if (cur == parent->_left)
			{
				parent->_balanceFactor--;
			}
			else if (cur == parent->_right)
			{
				parent->_balanceFactor++;
			}

			if (parent->_balanceFactor == 0)
			{
				break;
			}
			else if (parent->_balanceFactor == 1 || parent->_balanceFactor == -1) //平衡因子是1或-1
			{
				//继续向上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_balanceFactor == 2 || parent->_balanceFactor == -2) //平衡因子是2或-2
			{
				//旋转
				if (cur->_balanceFactor == -1 && cur == parent->_left) //插入在左子树的左子树中不平衡
				{
					RotateToRight(parent);
				}
				else if (cur->_balanceFactor == 1 && cur == parent->_right) //插入在右子树的右子树中不平衡
				{
					RotateToLeft(parent);
				}
				else if (cur->_balanceFactor == -1 && cur == parent->_right) //右左双旋
				{
					RotateToRightThenToLeft(parent);
				}
				else if (cur->_balanceFactor == 1 && cur == parent->_left) //左右双旋
				{
					RotateToLeftThenToRight(parent);
				}
				else
				{
					assert(false);
				}
			}
			else
			{
				assert(false);
			}
		}

		return true;
	}

	//右单旋
	void RotateToRight(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* grandParent = parent->_parent;

		//更新子树
		subL->_right = parent;
		parent->_left = subLR;
		parent->_parent = subL;
		if (subLR)
			subLR->_parent = parent;

		if (parent == _root) //父结点是根节点,更新根节点
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else //如果父结点不是根节点,那么要更新组父结点
		{
			subL->_parent = grandParent;
			if (grandParent->_left == parent)
			{
				grandParent->_left = subL;
			}
			else if (grandParent->_right == parent)
			{
				grandParent->_right = subL;
			}
		}

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

	//左单旋
	void RotateToLeft(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* grandParent = parent->_parent;

		//更新子树
		subR->_left = parent;
		parent->_right = subRL;
		parent->_parent = subR;
		if (subRL)
			subRL->_parent = parent;

		if (parent == _root)  //父结点是根节点,更新根节点
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else  //如果父结点不是根节点,那么要更新组父结点
		{
			subR->_parent = grandParent;
			if (grandParent->_left == parent)
			{
				grandParent->_left = subR;
			}
			else if (grandParent->_right == parent)
			{
				grandParent->_right = subR;
			}
		}

		//旋转后一定平衡,更新平衡因子
		subR->_balanceFactor = 0;
		parent->_balanceFactor = 0;
	}

	//右左双旋
	void RotateToRightThenToLeft(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int balanceFactor = subRL->_balanceFactor;

		RotateToRight(subR);
		RotateToLeft(parent);

		//subRL平衡因子决定最后的平衡因子
		if (balanceFactor == -1)
		{
			subRL->_balanceFactor = 0;
			parent->_balanceFactor = 0;
			subR->_balanceFactor = 1;
		}
		else if (balanceFactor == 1)
		{
			subRL->_balanceFactor = 0;
			parent->_balanceFactor = -1;
			subR->_balanceFactor = 0;
		}
		else if (balanceFactor == 0)
		{
			subRL->_balanceFactor = 0;
			parent->_balanceFactor = -1;
			subR->_balanceFactor = 0;
		}
		else
		{
			assert(false);
		}
	}

	//左右双旋
	void RotateToLeftThenToRight(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int balanceFactor = subLR->_balanceFactor;

		RotateToLeft(subL);
		RotateToRight(parent);

		if (balanceFactor == -1)
		{
			subLR->_balanceFactor = 0;
			parent->_balanceFactor = 1;
			subL->_balanceFactor = 0;
		}
		else if (balanceFactor == 1)
		{
			subLR->_balanceFactor = 0;
			parent->_balanceFactor = 0;
			subL->_balanceFactor = -1;
		}
		else if (balanceFactor == 0)
		{
			subLR->_balanceFactor = 0;
			parent->_balanceFactor = 0;
			subL->_balanceFactor = 0;
		}
		else
		{
			assert(false);
		}
	}
private:
	Node* _root = nullptr;
};
相关推荐
脑极体1 小时前
蓝河入海:Rust先行者vivo的开源之志
开发语言·后端·rust·开源
foxsen_xia1 小时前
go(基础01)——协程
开发语言·算法·golang
源代码•宸1 小时前
GoLang并发简单例子(goroutine + channel + WaitGroup)
开发语言·经验分享·后端·学习·golang
将心ONE1 小时前
pip导出项目依赖
开发语言·python·pip
稚辉君.MCA_P8_Java1 小时前
Gemini永久会员 Go 返回最长有效子串长度
数据结构·后端·算法·golang
A24207349301 小时前
js流程控制语句
开发语言·前端·javascript
kesifan1 小时前
JAVA的线程的周期及调度
java·开发语言
yngsqq1 小时前
二维异形排版、二维装箱(NPF碰撞检测)——CAD c#二次开发
开发语言·javascript·c#
TL滕1 小时前
从0开始学算法——第五天(初级排序算法)
数据结构·笔记·学习·算法·排序算法