AVL树的模拟实现

本文介绍了AVL树,一种自平衡二叉搜索树,通过维护平衡因子确保插入操作后树的高度差不超过1。文章详细阐述了AVL树的结构、插入过程中的平衡因子更新、旋转操作(左单旋、右单旋、左右双旋)以及计算树的高度和节点个数的方法。
摘要由CSDN通过智能技术生成

什么是AVL树?

二叉搜索树在插入时,可能会变成单支树,如图所示,这样的单支树在搜索时效率低,无法体现出二叉搜索树的优势,因此我们需要在插入的同时,调整二叉搜索树的高度,使每个节点的左右子树的高度差不超过1,这样的二叉搜索树称为 AVL树。

AVL的实现 

结点的定义:

AVL树的结点引入平衡因子(balance factor),平衡因子记录当前结点左右子树的高度差_bf = 右子树的高度 - 左子树的高度,可以更方便的调整每个节点左右子树的高度。引入父亲节点,方便调整高度的时候向上遍历。

    template<class K ,class V>
	struct AVLTreeNode
	{
		AVLTreeNode(const pair<K,V>& kv)
			:_kv(kv)
			,_bf(0)
			,_left(nullptr)
			,_right(nullptr)
			,_parent(nullptr)
		{ }
		pair<K,V> _kv;
		int _bf;//平衡因子
		AVLTreeNode<K, V>* _left;//左子树
		AVLTreeNode<K, V>* _right;//右子树
		AVLTreeNode<K, V>* _parent;//父亲节点
	};

插入 Insert :

平衡因子更新规则:

由于 _bf = 右子树的高度 - 左子树的高度,假设 cur 是要插入的结点,parent 是 cur 的父亲节点,

1、当 cur 在 parent 的左边 插入时,左子树的高度+1,故 parent->_bf --

2、当 cur 在 parent 的右边 插入时,右子树的高度+1,故 parent->_bf ++

当我们插入一个结点时,需要更新 parent 的 _bf:

1、当 parent->_bf == 0 时,说明插入前该子树左右高度相差1,插入后该子树左右高度相等,该子树已经平衡了,无需向上更新

2、 当 parent->_bf == 1/-1 时,说明插入前该子树左右高度相等,插入后该子树左右高度相差1,此时parent 需要向上走,继续更新平衡因子

3、当 parent->_bf == 2/-2 时,说明插入前该子树左右高度相差 1,插入后该子树左右高度相差 2,该子树已经不平衡了,需要对该子树进行旋转

旋转 :

左单旋 RotateL:

假设该子树的根节点为 parent,它的右子树的根节点为 subR,subR 的左子树为 subRL. 

右右:即在parent 的右子树(subR)的右子树插入结点。如图,a\b\c原本的高度为 h ,当我们在 60 的右子树 c 插入结点时,c 的高度变为 h+1,60 的平衡因子变为1,30的平衡因子变为2,这时我们需要调节根为30的树的高度,使每个节点左右子树的高度不超过1。

调整方式,即左单旋(把 subR 的左上角的结点 parent 旋转下来,作为 subR 的右子树),如下:

让 subRL 作为 30 的右子树,30 作为 60 的左子树,60 作为当前树的根。

因为修改了当前子树的根节点,现在需要更新该子树的根,令 parent 的父亲节点为 ppnode,让 ppnode 的孩子节点指向新的根,

1、当 parent 不是AVL的根时

a. 如果新的根在 ppnode 的左边,ppnode->_left = subR

b.如果新的根在 ppnode 的右边,ppnode->_right = subR

2、如果 parent 就是AVL树的根,即 ppnode 为空,我们需要修改树的根为 subR

最后对 parent / subR 的平衡因子进行修改。

        void RotateL(Node* parent)
		{//左单旋,要转下来的结点作为parent

			Node* subR = parent->_right;//父亲节点的右
			Node* subRL = subR->_left;

			parent->_right = subRL;
			if(subRL)//避免空指针的解引用
				subRL->_parent = parent;

			subR->_left = parent;
             //先设好ppnode,因为下一句会修改parent的父亲节点,导致ppnode不是我们预想的
            Node* ppnode = parent->_parent;
			parent->_parent = subR;

			if (parent == _root)
			{//如果转下来的结点是根
				_root = subR;
				subR->_parent = nullptr;
			}
			else
			{//如果转下来的结点不是根

				if (ppnode->_left == parent)//parent在ppnode的左边
				{
					ppnode->_left = subR;
					subR->_parent = ppnode;
				}
				else//parent在ppnode的右边
				{
					ppnode->_right = subR;
					subR->_parent = ppnode;
				}
			}

			//修改平衡因子
			subR->_bf = 0;
			parent->_bf = 0;
		}

右单旋 RotateR : 

假设该子树的根节点为 parent,它的左子树的根节点为 subL,subR 的左子树为 subLR. 

左左:即在parent 的左子树(subL)的左子树插入结点。如图,a\b\c原本的高度为 h ,当我们在 30 的左子树 a 插入结点时,a 的高度变为 h+1,30 的平衡因子变为 -1,60 的平衡因子变为 -2,这时我们需要调节该子树的高度。

调整方式,即右单旋(把 subL 的右上角的结点 parent 旋转下来,作为 subR 的右子树),如下:

思路参考左单旋:

        void RotateR(Node* parent)
		{//右单旋,要转下来的结点作为parent
			Node* subL = parent->_left;
			Node* subLR = subL->_right;

			parent->_left = subLR;
			if(subLR)
				subLR->_parent = parent;

			subL->_right = parent;
            Node* ppnode = parent->_parent;
			parent->_parent = subL;

			if (parent == _root)
			{
				_root = subL;
				subL->_parent == nullptr;
			}
			else
			{
				if (ppnode->_left == parent)
				{
					ppnode->_left = subL;
					subL->_parent = ppnode;
				}
				else
				{
					ppnode->_right = subL;
					subL->_parent = ppnode;
				}
			}
			parent->_bf = 0;
			subL->_bf = 0;
		}

右左双旋 RotateRL :

如图,假设插入前 b/c 的高度为 h-1,a/d 的高度为 h ,

在 b 插入结点时(左图),b 的高度变为 h,导致 60 的平衡因子变为 -1,90 的平衡因子变为 -1,30 的平衡因子变为 -2 ; 当在 c 插入结点时(右图),c 的高度变为 h,导致 60 的平衡因子变为 1,90 的平衡因子变为 -1,30 的平衡因子变为 2,此时仅通过左单旋/右单旋 无法使该子树平衡,需要右左双旋来解决。

1、当 a/b/c/d 的高度都不为 0 时,先对 90 的子树进行右单旋,此时子树仍不平衡,再对 30 的子树进行左单旋,通过两次旋转后,该子树平衡

2、当 a/b/c/d 的高度都为 0 时,即 60 就是新增的结点时,同样引发右左双旋。

 3、平衡因子的修改

上面三种情况旋转之后,parent / subR / subRL 的平衡因子的结果是不一样的,怎么区分这三种情况呢?

通过观察可知,subRL 的平衡因子在旋转前分别是 1 / -1 / 0,我们可以通过 subRL 旋转前的平衡因子来划分这三种情况

        void RotateRL(Node* parent)
		{
			Node* subR = parent->_right;
			Node* subRL = subR->_left;
			int bf = subRL->_bf;

			RotateR(subR);
			RotateL(parent);
			
			if (bf == 1)
			{
				parent->_bf = -1;
				subR->_bf = 0;
				subRL->_bf = 0;
			}
			else if (bf == -1)
			{
				parent->_bf = 0;
				subR->_bf = 1;
				subRL->_bf = 0;
			}
			else if (bf == 0)
			{
				parent->_bf = 0;
				subR->_bf = 0;
				subRL->_bf = 0;
			}
			else
				assert(false);
		}

左右双旋 RotateLR :

思路可以参考右左双旋:

1、当 a/b/c/d 的高度都不为 0 时,先对 30 的子树进行左单旋,再对 90 的子树进行右单旋

2、当 a/b/c/d 的高度都为 0 时, 60 就是新增的结点,和右左双旋的思路类似

        void RotateLR(Node* parent)
		{
			Node* subL = parent->_left;
			Node* subLR = subL->_right;
			int bf = subLR->_bf;

			RotateL(subL);
			RotateR(parent);

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

插入的实现:

如何判断是什么旋转类型?

结合上面的图片观察可得,我们通过 parent / cur 的平衡因子来判断。

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

			//根不为空
			Node* cur = _root;
			Node* parent = nullptr;			
			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->_right = cur;//插入在右
			}
			else
			{
				parent->_left = cur;//插入在左
			}
			cur->_parent = parent;//记得处理父亲结点
			
			while (parent)//一直更新到结点为空
			{
				if (parent->_left == cur)
				{
					--parent->_bf;
				}
				else
				{
					++parent->_bf;
				}

				if (parent->_bf == 0)
				{//二叉树已经平衡

					return true;
				}
				else if (parent->_bf == -1 || parent->_bf == 1)
				{//插入前parent->_bf为0

					cur = parent;//向上更新
					parent = parent->_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)
						RotateLR(parent);//左右双旋
					else
						RotateRL(parent);//右左双旋

					break;
				}
				else//插入前AVL树就有问题
					assert(false);
			}
			return true;
		}

AVL的高度 Height :

通过递归来计算树的高度,我们先递归左子树的高度,把递归的结果存为 leftHeight,再递归右子树的高度,把递归的结果存为 rightHeight,接着比较左右子树的高度,返回高的子树的高度 +1,这里 +1,加的是根节点自己

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

			size_t leftHeight = _Height(root->_left);
			size_t rightHeight = _Height(root->_right);

			return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
                                              //+1,加的是根结点自己
		}
		size_t Height()
		{
			return _Height(_root);
		}

AVL 树的结点的个数 Size : 

和计算高度的思路类似,计算个数也用递归实现,先计算左子树的结点的个数,再计算右子树的结点的个数,返回左右子树结点的个数的和 +1,这里 +1 同样加的是根节点自己。

        size_t _Size(Node* root)
		{//返回AVL树的结点的个数

			if (root == nullptr)
				return 0;

			return _Size(root->_left) + _Size(root->_right) + 1;//+1,加的是根结点自己			
		}
		size_t Size()
		{
			return _Size(_root);//同样需要套一层
		}

判断AVL树是否平衡 IsBalance : 

层层递归,先根据左右子树的高度来判断根的左右子树是否平衡,再判断当前根的子树是否平衡,

1、如果左右子树的高度差超过2,或者左右子树的高度差和根的平衡因子不相等,则该子树不平衡,返回 false,并且通过 || 的判断,层层递归 false 上去;

2、如果该子树平衡,则更新树的高度,并且返回 true,继续判断其他子树。

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

			int leftheight = 0;//左子树的高度
			int rightheight = 0;//右子树的高度

			if (!_IsBalance(root->_left, leftheight) 
             || !_IsBalance(root->_right, rightheight))
			{//左右子树有一个返回false,则直接返回假,并且层层返回假
				return false;
			}

			if (abs(rightheight - leftheight) >= 2)
			{//高度差超过2
				cout << "不平衡" << 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);
		}

  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值