C++ 二叉搜索树(二)AVL树的实现

本片基于上一篇实现的普通二叉搜索树BST实现更进一步的AVL树,AVL树在普通二叉搜索树的基础上添加了树的平衡操作

基础概念

节点的平衡因子:节点的左子树高度与右子树高度之差。

失衡:节点的平衡因子绝对值大于1。

平衡操作:通过左旋或右旋将失衡的节点的平衡因子绝对值恢复至小于等于1。

左旋:以下图为例,对节点A进行左旋,就是进行如下操作:

  • A成为其右子B的左子
  • B原来的左子T1成为A的右子

右旋:对节点A进行右旋,就是进行如下操作:

  • 节点A成为其左子B的右子
  • B原来的右子T2成为A的左子

上面的左旋和右旋已经涵盖了失衡中的2种情况,即ABC是在一个方向上的,下面说明不在一个方向时的旋转方法。

右左旋:如下图左所示,ABC不在同一个方向,此时,只需进行两次旋转:

  • B进行右旋操作
  • A使用左旋操作

这里画图的时候画错了,B和C应该互换。
在这里插入图片描述
在这里插入图片描述

左右旋:与上面的右左旋同理,不再赘述。

代码实现

注意:本节的实现基于上一篇实现的二叉搜索树BST

API

AVL树继承了普通了二叉搜索树BST,与BST唯一不同的仅仅是在插入和删除后添加了平衡操作

#define balancefactor(node) (getHeight(node->left) - getHeight(node->right))
#define isBlanced(node) (balancefactor(node) < 2 && balancefactor(node) > -2)

class AVL: public BST 
{
protected:
	Node* rotateAt(Node* node);
	Node* connect34(Node* a, Node* b, Node* c, 
				   Node* T0, Node* T1, Node* T2, Node* T3);
public:
	Node*& insert(int data);
	bool remove(int data);
};

由于旋转操作比较复杂且情况也比较多,具体实现中我们不使用旋转操作使二叉树复衡,而使用更加简单的、通用的3+4重构方法。

  • balancefactor()isBalanced()为计算平衡因子与判断节点是否平衡的宏。
  • insertremoveAVL树的插入和删除操作,与BST唯一不同的是添加了平衡操作。
  • rotateAtconnect34即3+4重构,实现平衡操作。

方法的实现

connect34

首先介绍3+4重构使AVL树恢复平衡的方法。

当失衡发生时,记参与旋转操作的3个节点为ABC,大小关系为A<B<C,同时,记这三个节点的4棵子树为T0T1T2T3,且这四棵子树中的节点满足T0<T1<T2<T3,那么我们只要按下图所示的样子重新组装,新组装的子树一定满足AVL树的条件:


你可以画一个失衡的树进行上面的操作,具体地查看一下。

代码实现
传入参数:3个节点和4棵子树。

返回值:新树的根节点,供调用者将重构完成的子树与整棵树进行链接,拿上面的图来说,返回的就是B

Node* AVL::connect34(Node* a, Node* b, Node* c,
					 Node* T0, Node* T1, Node* T2, Node* T3)
{
	a->left = T0; if (T0) T0->parent = a;
	a->right = T1; if (T1) T1->parent = a;
	updateHeight(a);

	c->left = T2; if (T2) T2->parent = c;
	c->right = T3; if (T3) T3->parent = c;
	updateHeight(c);

	b->left = a; a->parent = b;
	b->right = c; c->parent = b;
	updateHeight(b);

	return b;
}
rotateAt

该方法调用上面的connect34并指定重构子树的父节点
返回值:重构子树的根节点。

代码实现:

代码中g为失衡节点,pg的子,vp的子。

Node* AVL::rotateAt(Node* v)
{
	Node* p = v->parent, * g = p->parent;
    // 视4种不同情况调用connect
	if (p->isLeft()) {
		if (v->isLeft()) {
			p->parent = g->parent; // 指定重构子树父节点
            // 重构并返回根节点
			return connect34(v, p, g, v->left, v->right, p->right, g->right);
		}
		else {
			v->parent = g->parent;
			return connect34(p, v, g, p->left, v->left, v->right, g->right);
		}
	}
	else {
		if (v->isRight()) {
			p->parent = g->parent;
			return connect34(g, p, v, g->left, p->left, v->left, v->right);
		}
		else {
			v->parent = g->parent;
			return connect34(g, v, p, g->left, v->left, v->right, p->right);
		}
	}
}
insert

这个insert相比于BST中的insert添加了插入后的平衡操作(调用上面的3+4重构)。

要注意的是,在平衡过后,子树的高度与未插入节点前是一样的,因此只需进行至多一次平衡操作。

Node*& AVL::insert(int data)
{
	Node*& node = search(data);
	if (node) return node;
	++size_;
	node = new Node(data, hot_);  // 插入新节点
	if (!hot_) root_ = node; // 此时插入的节点为根节点,更新root_
	// 重平衡
	for (Node* p = node->parent; p; p = p->parent) {
		if (!isBlanced(p)) { // 如果发现失衡
			// 先记录要被重构的子树的父亲
			Node* temp = p->parent;
			// 重构
			Node* newp = rotateAt((p->getTallerChild())->getTallerChild());
			// 更新重构子树在其父亲的子节点中的位置
			if (temp) {
				if (p == temp->left) temp->left = newp;
				else temp->right = newp;
			}
			// 如果重构子树根为整树的根,那么更新root_
			if (p == root_) root_ = newp;
			// 重构后子树高度与未插入前相同,因此不用更新高度
			break;
		}
		else {
			updateHeight(p);
		}
	}
	return node;
}
remove

同样在删除后添加了平衡操作,与插入不同的是,子树平衡后高度与未删除前不一定相同,因此更高的祖先可能失衡,需要进行O(logn)次的平衡操作。

bool AVL::remove(int data)
{
	Node*& target = search(data);
	if (!target) return false;
	removeAt(target);
	--size_;
	// 重平衡
	for (Node* p = target->parent; p; p = p->parent) {
		if (!isBlanced(p)) { // 如果发现失衡
			// 先记录要被旋转的子树的父亲
			Node* temp = p->parent;
			// 旋转并返回该子树的新树根
			Node* newp = rotateAt((p->getTallerChild())->getTallerChild());
			// 更新子树在其父亲的子节点中的位置
			if (temp) {
				if (p == temp->left) temp->left = newp;
				else temp->right = newp;
			}
			// 如果旋转子树根为整树的根,那么更新root_
			if (p == root_) root_ = newp;
			updateHeight(p); // 高度更新
		}
	}
	return true;
}

所有二叉搜索树的代码已上传githubsir, this way.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值