C++AVL树

目录

1、AVL树的概念

2、AVL树的基本操作

2.1创建一个节点

2.2AVL树的插入

2.2.1按照二叉搜索树的方式插入一个新的节点

2.2.2调整节点的平衡因子

2.2.3旋转

2.2.3.1左单旋

2.2.3.2右单旋

2.2.3.3左右双旋

2.2.3.4右左双旋

2.2.3.5总结

3、检查是否为AVL树

4、总代码


1、AVL树的概念

AVL树又被称为平衡二叉搜索书,它是俄罗斯的两位数学家G.M. Adelson-Velsky 和 E.M. Landis名字的缩写。

搜索二叉树虽然可以缩短查找的效率,但如果当一组数据有序或接近有序时,这会导致搜索二叉树退化成单支树,查找的效率就会相当于在顺序表中查找的效率,效率低下。AVL树的出现就是为了解决这一问题:当向二叉树中插入一个新节点后,要保证该树中每个节点的左右子树的高度差不超过1,这就可以降低树的高度,从而减少搜索长度

AVL树本质上是一棵二叉搜索树,但它还具有以下性质:

  • 它的左右子树都是AVL树
  • 左右子树的最大高度差(简称平衡因子,右子树的最大高度-左子树的最大高度)不超过1(-1/0/1)

2、AVL树的基本操作

2.1创建一个节点

由于AVL树的底层是一棵二叉搜索树,因此AVL树的节点内存储的变量与普通二叉搜索数的变量基本相似,但是由于该树要构成一颗三叉链,因此内部要存储一个父节点的指针。为了平衡因子计算的方便,可以在该节点中加入一个整形用来存储平衡因子,但这并不是必须的

template<class T>
struct AVLTreeNode
{
	AVLTreeNode(const T& val)
		:_left(nullptr), _right(nullptr)
		, _parent(nullptr), _val(val), _bf(0) //一个新节点左右都为空,故平衡因子初始化为0
	{ }
	AVLTreeNode* _left; //节点的左孩子
	AVLTreeNode* _right; //节点的右孩子
	AVLTreeNode* _parent; //节点的父亲

	T _val;
	int _bf; //该节点的平衡因子
};

2.2AVL树的插入

AVL树的插入就是在二叉搜索树插入的基础上引入了平衡因子,因此AVL树的插入可以分为两部分:

  1. 按照二叉搜索树的方式插入一个新的节点
  2. 调整节点的平衡因子

2.2.1按照二叉搜索树的方式插入一个新的节点

	bool insert(const T& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(val);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;

		while (cur) //直到cur == nullptr循环结束
		{
			if (val > cur->_val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (val < cur->_val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else //val == cur->_val,插入失败
			{
				return false;
			}
		}
		cur = new Node(val);
		if (cur->_val > parent->_val)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;
	}

2.2.2调整节点的平衡因子

插入新节点后可能会导致其父节点的平衡因子发生改变,在插入新节点前,父节点的平衡因子有以下三种情况:-1/0/1,插入新节点后,父节点的平衡因子变化有两种方式:

  1. 插入的节点位于父节点的左边,父节点的平衡因子-1
  2. 插入的节点位于父节点的右边,父节点的平衡因子+1

插入新节点后会导致其父节点的平衡因子有以下几种情况:-2/-1/0/1/2

  1. 如果此时父节点的平衡因子为0,这说明在插入新节点前父节点的平衡因子为1或-1,插入后被调整为0,这表明该树的高度没有改变,且满足AVL树的性质,因此不需要进行调整
  2. 如果此时父节点的平衡因子为-1或1,这说明在插入新节点前父节点的平衡因子为0,插入后被调整为-1或1,这说明树的高度发生了改变,因此需要继续向上更新平衡因子,直到平衡因子再次为0或更新至根节点
  3. 如果此时父节点的平衡因子为-2或2,这说明在插入新节点前父节点的平衡因子为-1或1,插入后被调整为-2或2,这说明树的高度发生了改变,且由于该树不符合AVL树的性质,因此需要对其进行旋转处理

2.2.3旋转

AVL树的旋转分为单旋和双旋,单旋被分为左单旋和右单旋,双旋被分为左右双旋和右左双旋

2.2.3.1左单旋

从上图中我们可以看到,当在c这棵树中插入节点时,c的高度变成了h+1,此时60这个节点的平衡因子变为1,故需要向上传递,30的平衡因子就会变成2,这时就会导致这棵树不符合AVL树的性质,因此会触发旋转,由于高的一侧位于树的右边所以要进行左单旋。左单旋就是使60这个节点变为这棵树根节点,30的右子树的指针指向60的左子树b,60的左子树的指针指向30这个节点,这样就完成了一次左单旋,旋转结束后要记得更新平衡因子。

在节点30的右节点的右子树上插入新的节点,我们成这种情况叫做右右,要用到旋转

在旋转过程中要考虑一下几个事项:

  1. 60的左孩子可能存在,也可能不存在,因此在调整指针指向时要特别注意_parent指针
  2. 30可能是根节点,也可能是一棵子树。如果是根节点,在旋转结束后要及时更新根节点的指向;如果是一棵子树,就要判断这棵子树是在左子树还是在右子树
//if (parent->_bf == 2 && cur->_bf == 1) //新节点位于右节点的右子树
//{
//    RotateL(parent);
//}

void RotateL(Node* parent)
{
	Node* subr = parent->_right;
	Node* subrl = subr->_left;
	Node* pparent = parent->_parent;

	parent->_right = subrl;
	if (subrl)
		subrl->_parent = parent;
	subr->_left = parent;
	parent->_parent = subr;

	if (_root == parent)
	{
		_root = subr;
		subr->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
		{
			pparent->_left = subr;
		}
		else
		{
			pparent->_right = subr;
		}
		subr->_parent = pparent;
	}
	parent->_bf = subr->_bf = 0;
}
2.2.3.2右单旋

从上图中我们可以看到,当在a这棵树中插入节点时,a的高度变成了h+1,此时30这个节点的平衡因子变为-1,故需要向上传递,60的平衡因子就会变成-2,这时就会导致这棵树不符合AVL树的性质,因此会触发旋转,由于高的一侧位于树的左边所以要进行右单旋。右单旋就是使30这个节点变为这棵树根节点,60的左子树的指针指向30的右子树b,30的右子树的指针指向60这个节点,这样就完成了一次右单旋,旋转结束后要记得更新平衡因子。

在节点60的节点的左子树上插入新的节点,我们成这种情况叫做左左,要用到旋转

在旋转过程中要考虑一下几个事项:

  1. 30的右孩子可能存在,也可能不存在,因此在调整指针指向时要特别注意_parent指针
  2. 60可能是根节点,也可能是一棵子树。如果是根节点,在旋转结束后要及时更新根节点的指向;如果是一棵子树,就要判断这棵子树是在左子树还是在右子树
//else if (parent->_bf == -2 && cur->_bf == -1)
//{
//	  RotateR(parent);
//}
void RotateR(Node* parent)
{
	Node* subl = parent->_left;
	Node* sublr = subl->_right;
	Node* pparent = parent->_parent;

	parent->_left = sublr;
	if (sublr)
		sublr->_parent = parent;
	subl->_right = parent;
	parent->_parent = subl;

	if (_root == parent)
	{
		_root = subl;
		subl->_parent = nullptr;
	}
	else
	{
    	if (pparent->_left == parent)
		{
			pparent->_left = subl;
		}
		else
		{
			pparent->_right = subl;
		}
		subl->_parent = pparent;
	}
	parent->_bf = subl->_bf = 0;
}
2.2.3.3左右双旋

从上图我们可以看到在90这个节点的左节点的右子树上插入一个新节点导致90的平衡因子为2,因此这里我们就需要进行旋转,但是我们发现只进行一次右旋无法使这棵树符合AVL树的性质,因此我们这里就需要对这棵树进行双旋处理。这里一共分成两步进行:

  1. 以30为中心进行左旋,这样使60的左树接到了30的右边,这就使得这棵树符合左旋的条件,即相当于在90的左节点的左子树上插入了一个节点,这里我们就进行第二步
  2. 以60为中心进行右旋,详细步骤可以看上面

在节点90的左节点的右子树上插入一个新节点,我们将这种情况称为左右型,因此我们需要进行左右双旋

在旋转前,需要保存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;
		sublr->_bf = 0;
		subl->_bf = -1;
	}
	else if (bf == -1)
	{
		parent->_bf == 1;
		sublr->_bf = 0;
		subl->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = sublr->_bf = subl->_bf = 0;
	}
	else
	{
		assert(false);
	}
}
2.2.3.4右左双旋

从上图我们可以看到在30这个节点的右节点的左子树上插入一个新节点导致30的平衡因子为2,因此这里我们就需要进行旋转,但是我们发现只进行一次左旋无法使这棵树符合AVL树的性质,因此我们这里就需要对这棵树进行双旋处理。这里一共分成两步进行:

  1. 以90为中心进行左旋,这样使60的右树接到了90的左边,这就使得这棵树符合右旋的条件,即相当于在30的右节点的右子树上插入了一个节点,这里我们就进行第二步
  2. 以30为中心进行左旋,详细步骤可以看上面

在节点30的节点的左子树上插入一个新节点,我们将这种情况称为右左型,因此我们需要进行右左双旋

在旋转前,需要保存60这个节点的平衡因子,因为旋转结束后,需要根据该平衡因子去调整其他节点的平衡因子

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;
		subrl->_bf = 0;
		subr->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf == 0;
		subrl->_bf = 0;
		subr->_bf = 1;
	}
	else if (bf == 0)
	{
		parent->_bf = subrl->_bf = subr->_bf = 0;
	}
	else
	{
		assert(false);
	}
}
2.2.3.5总结
插入位置状态操作
父节点的左节点的左子树左左型右旋
父节点的左节点的右子树左右型左右旋
父节点的右节点的右子树右右型左旋
父节点的右节点的左子树右左型右左旋

3、检查是否为AVL树

检查一棵树是否为AVL树不能直接去查它的平衡因子,这是因为你不确定平衡因子是否正确,且这棵树不一定存储了平衡因子。因此,要去检查这棵树是否为AVL树可以通过计算这棵树左子树和右子树高度的差值进行检验

int Height(Node* root) //计算树的高度
{
	if (root == nullptr)
	{
		return 0;
	}
	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);

	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
bool _IsBalance(Node* root)
{
	if (root == nullptr)
	{
		return true;
	}
	int leftHeight = Height(root->_left);
	int rightHeight = Height(root->_right);

	if (rightHeight - leftHeight != root->_bf)
	{
		cout << "平衡因子异常" << endl;
		return false;
	}
	return abs(rightHeight - leftHeight) < 2
		&& _IsBalance(root->_left) && _IsBalance(root->_right); //每棵子树同样需要是AVL树
}

4、总代码

template<class T>
struct AVLTreeNode
{
	AVLTreeNode(const T& val)
		:_left(nullptr), _right(nullptr)
		, _parent(nullptr), _val(val), _bf(0)
	{
	}
	AVLTreeNode* _left; //节点的左孩子
	AVLTreeNode* _right; //节点的右孩子
	AVLTreeNode* _parent; //节点的父亲

	T _val;
	int _bf; //该节点的平衡因子
};

template<class T>
class AVLTree
{
	typedef AVLTreeNode<T> Node;
public:
	AVLTree()
		:_root(nullptr)
	{
	}
	bool insert(const T& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(val);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur) //直到cur == nullptr循环结束
		{
			if (val > cur->_val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (val < cur->_val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else //val == cur->_val,插入失败
			{
				return false;
			}
		}
		cur = new Node(val);
		if (cur->_val > parent->_val)
		{
			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) //bf为0,该树高度不变,不需要调整
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1) //bf为1或-1,该树高度改变,继续向上调整
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2) //bf为2或-2,该树高度改变,触发旋转
			{
				if (parent->_bf == 2 && cur->_bf == 1) //右右型
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1) //左左型
				{
					RotateR(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1) //左右型
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1) // 右左型
				{
					RotateRL(parent);
				}
				else //不属于以上则说明平衡因子异常
				{
					assert(false);
				}
				break; //旋转结束后,这棵树符合AVL树性质,结束循环

			}
			else //走到这里说明树的平衡因子异常,超出范围,该树的平衡因子调整错误,对代码重新检查
			{
				assert(false);
			}
		}
		return true;
	}
	bool IsBalance()
	{
		return _IsBalance(_root);
	}
private:
	int Height(Node* root)
	{
		if (root == nullptr)
		{
			return 0;
		}
		int leftHeight = Height(root->_left);
		int rightHeight = Height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		int leftHeight = Height(root->_left);
		int rightHeight = Height(root->_right);

		if (rightHeight - leftHeight != root->_bf)
		{
			cout << "平衡因子异常" << endl;
			return false;
		}
		return abs(rightHeight - leftHeight) < 2
			&& _IsBalance(root->_left) && _IsBalance(root->_right);
	}
	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;
			subrl->_bf = 0;
			subr->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subrl->_bf = 0;
			subr->_bf = 1;
		}
		else if (bf == 0)
		{
			parent->_bf = subrl->_bf = subr->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	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;
			sublr->_bf = 0;
			subl->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
			sublr->_bf = 0;
			subl->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = sublr->_bf = subl->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void RotateR(Node* parent)
	{
		Node* subl = parent->_left;
		Node* sublr = subl->_right;
		Node* pparent = parent->_parent;

		parent->_left = sublr;
		if (sublr)
			sublr->_parent = parent;
		subl->_right = parent;
		parent->_parent = subl;

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

	void RotateL(Node* parent)
	{
		Node* subr = parent->_right;
		Node* subrl = subr->_left;

		parent->_right = subrl;
		subr->_left = parent;

		Node* pparent = parent->_parent;
		parent->_parent = subr;

		if (subrl)
			subrl->_parent = parent;

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

	Node* _root;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值