手撕AVL树

AVL树

VAL树是一种高度自动平衡的二叉搜索树,它具有稳定的二叉树造型,对于普通搜索二叉树而言,如果遇到了有序的数据,它就会变成一遍到,形同链表的造型,对搜索效率的损伤特别大,而VAL树的自动平衡会调整结构,使二叉树达到高度平衡的造型,让搜索效率大大提高

   VAL树的特点

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

VAL树的节点

struct VALtreeNode
{

	VALtreeNode* left;
	VALtreeNode* right;
	VALtreeNode* _parent;
	pair<T, K>_kv;
	int _bf;
	VALtreeNode(const pair<T,K>&kv)
		:left(nullptr)
		, right(nullptr)
		, _parent(nullptr)
		,_bf(0)
		,_kv(kv)
	{

	}
};

VAL树的节点相对于普通二叉树多了父亲节点的指针,形成三叉链的结构,另外多了一个int类型的变量_bf当做平衡因子,用来判断搜索二叉树是否平衡,当该变量的值不是1 -1 0当中的任何一个时,代表二叉树出现平衡失调,需要进行调整,该变量是由右子树的高度减去左子树的高度得到的,在插入新节点的时候,就可以更新该值,如插入右节点的时候,该值加加,插入左节点的时候,该值减减。

AVL树节点的插入 

       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;
			}
		}

前面部分正常,寻找新节点的位置,当插入值大于当前节点值,去右子树中找位置,当插入值小于当前节点值,去左子树中寻找位置,并且每次更新节点时,要将该节点的位置保留在parent当中。后序链接新节点,对_parent赋值都需要用到。

        cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->right = cur;
			cur->_parent = parent;
		}

如果新插入的值比父亲点值小,则链接在父亲节点的左边,如果比父亲节点值大,则链接在父亲节点的右边,同时更新新节点的父亲节点指针。

链接好后,需要更新父亲节点的平衡因子,因为新插入的节点左右子树为空,所以它的平衡因子为零。

          while (parent)
		{
			if (parent->left == cur)
				parent->_bf--;
			else
				parent->_bf++;
			int _bf = parent->_bf;
			if (_bf == 0)
			{
				break;
			}
			else if (_bf == 1 || _bf == -1)//代表可能是父亲的父亲节点出现错误,更新parent和 
            cur,去上面寻找异常。
			{
				cur = parent;
				parent = parent->_parent;
			}
            else
            {
               ..........
            }
        }

 当父亲节点的平衡因子为零时,不会对上面的节点产生影响,因为父亲节点所在的子树的高度是没有变化的,不过是父亲节点的左右子树高度达到了平衡,整个包括父亲节点所在的子树的高度并未发生变化。

当父亲节点的平衡因子为1或者-1是,代表父亲节点所在的子树高度产生了变化,会对上面节点的平衡因子产生影响,这时就需要更新parent和cur的值,对上面的平衡因子不断更新,直到到达起始源根停止,如果出现平衡因子不稳定的情况,则需要做出相应调整。

当父亲节点的平衡因子为2或者-2时,代表需要对二叉树的结构进行调整。

            else//代表出现异常,需要调整
			{
				if (parent->_bf == 2 && cur->_bf == 1)//
				{
					roateL(parent);
					break;
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					roateR(parent);
					break;
				}
				else if (parent->_bf = 2 && cur->_bf == -1)
				{
					roateRL(parent);
					break;
				}
				else if (parent->_bf = -2 && cur->_bf == 1)
				{
					roateLR(parent);
					break;
				}
            }

 需要调整的情况主要是4种,这四种情况以及对应的调整方法就可以应对成千上万的平衡失调。

第一种 parent->_bf==2

当父亲节点的平衡因子为2时,代表右边出现一边高的情况,即左子树的高度比右子高2

我们采用左单旋的调整方式来纠正,但左单旋会有以下两种情况。

void roateL(Node*parent)//左旋转 1.subRL可能为空 2.paretn不一定为空 3.需要改变父节点 4.父亲节点的改变需要判断。
	{
		Node* grandfather = parent->_parent;
		Node* subR = parent->right;
		Node* subRL = subR->left;
		parent->right = subRL;
		subR->left = parent;
		parent->_parent = subR;
		if (subRL)
		{
			subRL->_parent = parent;
		}
		if (_root == parent)
		{
			subR->_parent = nullptr;
		}
		else
		{
			if (grandfather->left == parent)
			{
				grandfather->left = subR;
			}
			else
			{
				grandfather->right = subR;
			}
			subR->_parent = parent;
		}
		parent->_bf = subR->_bf = 0;
		            
	}

左单旋:以父节点为旋转中心

1.将subR的左子树赋值给父节点的左孩子指针

2.将父节点赋值给subR的右孩子指针

3.改变关键节点的父子关系

最后形成,以subR为父节点的新子树

注:subR为父亲节点的右子树,subRL为subR的左子树,parent为父节点,其中subRL可能为空,需要加以判断。

左单旋中,h不为零的情况其实是有很多种,也就是a,b,c有多种形态,但归根结底都是右子树的高度比左子树的高度高出2,可以统一用一种方法处理。

第二种 paretn->_bf==-2

当父亲结点的平衡因子为-2时,代表左子树的高度比右子树高2,这时候就需要用右单旋来调整高度。

 

void roateR(Node* parent)//右旋转
	{
		Node* grandfather = parent->_parent;
		Node* subL = parent->left;
		Node* subLR = subL->right;
		parent->left = subLR;
		if (subLR)
		{
			subLR->_parent = parent;
		}
		subL->right = parent;
		parent->_parent = subL;
		if (_root == parent)
		{
			subL->_parent = nullptr;
		}
		else
		{
			if (grandfather->left == parent)
			{
				grandfather->left = subL;
			}
			else
			{
				grandfather->right = subL;
			}
			subL->_parent = parent;
		}
		subL->_bf = parent->_bf = 0;

	}

右单旋:以父节点为旋转中心

  1.  将subL的右子树赋值给父亲节点左子树指针
  2. 将父亲节点赋值给subL的右子树指针
  3. 改变关键节点的赋值关系

同左单旋一样,在旋转的时候,需要注意subLR是否存在,有可能为空,必须判断一下,否则会运行报错。

第三种 parent->_bf==2&&cur->_bf==-1

父亲节点的平衡因子为2,代表父亲节点的右子树比左子树高出2,而cur为父亲节点的右子树根,它的平衡因子为-1,代表它的左子树比右子树高一,也就是说,新插入的节点是插入在cur的左子树当中,从而引起父亲节点平衡因子的失衡。

这种失衡情况特殊,解决方法是右左双旋,先以cur为旋转中心,右单旋,使整个树造型变为单纯右边高2,然后再左单旋

void roateRL(Node* parent)
	{
		Node* subR = parent->right;
		Node* subRL = subR->left;
		int bf = subR->_bf;
		roateR(subR);//先以parent的右子树为中心右旋转
		roateL(parent);//在以parent的左子树为中心左旋转,此刻该阶段的树已经变成了左边一遍高的树了
		if (bf == 0)
		{
			subR->_bf = parent->_bf = subRL->_bf = 0;
		}
		else if(bf == 1)//在subRL的右子树插入
		{
			subRL->_bf = 0;
			parent->_bf = -1;
			subR->_bf = 0;
		}
		else if(bf == -1)//在subRL的左子树插入
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			subR->_bf = 1;
		}
		else
		{
			assert(false);
		}

}

 双旋内部又会有不同的情况,对应的不同情况有不同的平衡因子调节方式,我们先旋转,在最后来彻底更新平衡因子。

右左双旋是先右单旋,再左单旋,我们可以用直接调用之前的函数,提高代码的复用率,在日常中,能够复用尽量复用。

第四种 parent->_bf==-2&&cur->_bf==1

即父亲节点的平衡因子为-2,代表父亲节点的左子树比右子树高出2,而cur作为左子树的根,它的平衡因子为1,代表它的右子树比左子树高1,也就是说,新插入的节点是插入在cur的右子树当中。

这种情况是用左右双旋来完成调整,即先左单旋,使其造型变为右单旋,再用右单旋收尾。

 

void roateLR(Node* parent)//左右双旋转
	{
		Node* subL = parent->left;
		Node* subLR = subL->right;
		int bf = subL->_bf;
		roateL(subL);
		roateR(parent);
		if (bf == 0)
		{
			subL->_bf = parent->_bf = subLR->_bf = 0;
		}
		if (bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
			subLR->_bf = 0;
		}
		if (bf == -1)
		{
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
	}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值