【C++】高阶数据结构 -- 平衡二叉树(AVLTree)

本篇文章主要讲解一种特殊的 BST -- 平衡二叉树(AVLTree)


目录

[1 平衡二叉树的概念](#1 平衡二叉树的概念)

[2 AVLTree 的增删查改(不允许插入重复 key 的 key-value 场景)](#2 AVLTree 的增删查改(不允许插入重复 key 的 key-value 场景))

[1) AVLTree 的节点结构](#1) AVLTree 的节点结构)

[2) AVLTree 的插入](#2) AVLTree 的插入)

[(1) 如何更新平衡因子](#(1) 如何更新平衡因子)

[(2) AVL 树的旋转](#(2) AVL 树的旋转)

[3) AVLTree 的查找](#3) AVLTree 的查找)

[3 AVLTree 的检测](#3 AVLTree 的检测)

[4 总结](#4 总结)


1 平衡二叉树的概念

平衡二叉树是一种特殊的二叉搜索树,它不仅满足二叉搜索树的性质,而且在二叉搜索树的基础上实现了自平衡,能使得左子树与右子树的高度差不超过1,从而提高了查找效率。

AVLTree 是最早的平衡二叉树,其是由两位苏联数学家 Georgy Adelson-Velsky 和 Evgenii Landis 在 1962 年发表在 《An algorithm for the organization of information》论文上。AVLTree 的明名正式取自这两位发明者的名字首字母。

在 AVLTree 中每个节点都有一个平衡因子 (balance factor) ,该平衡因子可以是左子树的高度减去右子树的高度,也可以是右子树的高度减去左子树的高度,我们这里选择平衡因子为右子树的高度减去左子树的高度 ,而对于 AVLTree 来说,每个节点左右子树的高度差不能超过1,所以平衡因子只能是 0、1、-1。这里的平衡因子不是必须的,但是有了平衡因子可以帮助我们观察和检测整棵 AVLTree 的结构。

那么根据 BST 的结构,AVLTree 的结构也与之类似。AVLTree 要么是一棵空树,要么就是一棵如下结构的二叉树:

(1) 每个节点的左子树和右子树的高度差不能超过1,也就是平衡因子只能为 0、1、-1

(2) 每个节点的左子树又是一棵 AVLTree

(3) 每个节点的右子树又是一棵 AVLTree

以下的这棵树就是一棵 AVLTree:

红色的就是每个节点的平衡因子。需要注意的是 AVLTree 也是一棵 BST,在满足平衡的同时也需要满足二叉搜索树的规则。

以下的这棵树就不是一棵 AVLTree:

因为其有一个节点的平衡因子为 2,大于1,所以其不是一棵 AVLTree。

所以对于 AVLTree 来说,由于左右子树的高度差不能超过1,所以其高度会和完全二叉树类似,高度可以控制在 logN 级别,而二叉搜索树的搜索次数就是其高度次,所以AVLTree 增删查改的效率就是 logN 级别,比起 BST 来说会有很大的提升。


2 AVLTree 的增删查改(不允许插入重复 key 的 key-value 场景)

1) AVLTree 的节点结构

由于实现的 key-value 场景,所以在节点里面我们直接使用 pair<K, V> 对象来作为数据类型,另外左孩子、右孩子也必不可少。在上一小节里面,我们知道了在 AVLTree 里面可以利用一个平衡因子(balance factor)来帮助我们检测整棵 AVLTree 的结构,所以我们选择在节点结构里面再添加一个平衡因子 _bf,帮助我们检测与更新 AVL 树。

那么节点里面有没有别的需要添加的成员变量呢?在这里我们选择添加一个 _parent 指针变量来指向当前节点的父亲节点。添加该成员变量的原因是因为在后面更新 AVL 树的结构时方便找到父节点,但是你不添加 _parent 也是可以的,只是添加了之后更加方便。

cpp 复制代码
template<class K, class V>
struct AVLNode
{
    pair<K, V> _data;
    AVLNode<K, V> _left;
    AVLNode<K, V> _right;
    AVLNode<K, V> _parent;
    int _bf;

    AVLNode(const pair<K, V>& data)
        :_data(data)
        ,_left(nullptr)
        ,_right(nullptr)
        ,_parent(nullptr)
        ,_bf(0)
    {}
};

那么有了节点结构之后,整棵树的结构也就很好写了。因为只需要有一个根就可以找到整棵树,那么我们只需要一个根节点的成员变量就可以了:

cpp 复制代码
template<class K, class V>
class AVLTree
{
    typedef AVLNode<K, V> Node;
public:
private:
    Node* _root = nullptr;
};

2) AVLTree 的插入

由于 AVL 树依然是一棵 BST,所以插入节点前面情况与 BST 插入是相同的,也就是:

(1) 如果树为空,直接创建当前节点

(2) 创建 parent 指针与 cur 指针,parent = nullptr, cur = _root

(3) 根据要插入数据 key 的大小来判断插入到左边还是右边,直到 cur 为空

(4) 创建节点,改变 parent 节点指向与 cur 中的 _parent 指针

具体代码为:

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

	Node* cur = _root, *parent = nullptr;
	while (cur)
	{
		if (data.first > cur->_data.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (data.first < cur->_data.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
			return false;
	}

	//找到了该插入的位置
	cur = new Node(data);
	//更改parent 指向
	if (data.first > parent->_data.first)
		parent->_right = cur;
	else
		parent->_left = cur;

	//不要忘记更改 cur 的 _parent 指针
	cur->_parent = parent;

	return true;
}

但是在 AVLNode 中还有一个 _bf 平衡因子成员,我们需要来更新它。更新平衡因子比较复杂,接下来我们来看一下如何更新平衡因子。

(1) 如何更新平衡因子

平衡因子更新原则:

之前在 AVLTree 的概念那一小节我们选择平衡因子 = 右子树高度 - 左子树高度,所以在新插入一个节点之后,首先就会对其 parent 节点的高度产生影响,我们需要先更新 parent 节点的平衡因子,也就是如果在右子树插入,那么右子树的高度就会 +1,所以 ++_bf 就可以;如果插入到左子树,那么左子树的高度就会 +1,需要 --_bf。那么这里只更新 parent 节点的平衡因子就可以吗?我们来看这么一种情况:

红色节点为新插入的节点,将 parent 节点的平衡因子更新为了 -1,但是看 38 的 parent 节点 30,其右子树的高度变为了 2,所以应该也将其更新为 1,对于根节点也是一样的,由于其右子树节点高度变为了 3,所以其平衡因子应该更新为 0。所以插入一个节点之后,不仅要更新当前节点父亲节点的平衡因子,其所有祖先的平衡因子都要更新。究其原因就是因为如果插入节点的 parent 子树高度变化了,其所有祖先的高度也必然会发生变化,所以所有祖先的平衡因子都要更新。

总结一下平衡因子更新原则

a. 平衡因子 = 右子树高度 - 左子树高度

b. 插入在左子树,parent 的平衡因子 ++;插入在右子树,parent 的平衡因子 --

c. 如果 parent 的子树高度变化了,就需要向上更新,直到其祖先节点的高度不变或者更新到根节点

平衡因子停止更新的条件:

a. 如果 parent 节点的平衡因子更新为了 0,也就是由 1->0 或者 -1->0,说明更新之后,其左右子树的高度相等了,那就不需要再进行更新了,更新结束

b. 如果 parent 节点的平衡因子更新为了 1 或者 -1,那就说明其子树的高度改变了,那就需要继续向上更新

c. 如果 parent 节点的平衡因子更新为了 2 或者 -2,那就说明节点是插入到了更高的子树里,这时候整棵树已经不平衡了,所有的平衡因子不是 1,0,-1 了,这时候需要进行旋转处理,将 parent 及其子树重新旋转成为一棵 AVL 树,进行旋转之后,整棵树会恢复平衡,此时也不需要进行平衡因子的更新了,更新结束

d. 如果一直更新到根,更新完 _root 节点的平衡因子之后,更新也会结束

以下是对应于上面四种情况的图形:

a:

此时 parent->_bf 更新为 0,不影响其他祖先的平衡因子,所以更新结束。

b 上面已经演示过了,就不再演示。

c:

更新平衡因子时出现了 -2,此时需要进行旋转处理,旋转之后的树会变为:

旋转完之后,其就变成了一棵 AVL 树,所以就不需要更新平衡因子了,直接退出即可。

d 比较简单,就不演示了。

所以插入完节点之后,我们需要更新平衡因子,而且需要更新到根。这部分代码为:

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

	Node* cur = _root, *parent = nullptr;
	while (cur)
	{
		if (data.first > cur->_data.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (data.first < cur->_data.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
			return false;
	}

	//找到了该插入的位置
	cur = new Node(data);
	//更改parent 指向
	if (data.first > parent->_data.first)
		parent->_right = cur;
	else
		parent->_left = cur;

	//不要忘记更改 cur 的 _parent 指针
	cur->_parent = parent;

	//更新平衡因子
	while (parent)
	{
		if (cur == parent->_left)
			--parent->_bf;
		else
			++parent->_bf;

		//根据不同情况来判断是否向上更新
		if (parent->_bf == 0)
			//不更新了,直接退出
			break;
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			//继续向上更新
			cur = parent;
			parent = parent->_parent;
		}
		else if (parent->_bf == 2 || parent->_bf == -2)
		{
			//旋转处理
			//结束之后,也不需要更新了
			break;
		}
		else
		{
			//为了处理平衡因子出错的情况
			assert(false);
		}
	}

	return true;
}

接下来就是 AVL 树里面最难的部分,也就是如何将一棵不平衡树旋转成为一棵平衡树,我们来仔细讲解这部分。

(2) AVL 树的旋转

在 AVL 树中为了使得整棵树保持平衡的特性,以保持较高的查找效率,当有节点出现平衡因子为 2 或者 -2 时,AVL 树会进行旋转的操作,使得所有节点的平衡因子重新变为 1,0,-1。

AVL 树中共有四种旋转方式,分别是右单旋、左单旋、左右双旋、右左双旋,分别对应四种不同的情况。

右单旋

如果出现了以下的情况,我们会对整棵树进行右单旋:

当 10 插入在 15 的左子树里面时,使得本来就高的左子树变得更加高时,这时候就要采取右单旋的形式。如果用一个抽象图来表示的话就是这样的:

20 的平衡因子本来是 -1,左子树高度为 h+1,右子树高度为 h,但是在 14 的左子树 A 插入一个节点之后使得 20 的左子树高度变为了 h+2,也就是在本来就高的左子树又插入了一个节点,使得平衡因子变为了 -2,此时就需要使用右单旋来解决这种场景。

右单旋的旋转步骤

这里就以上面那幅抽象图演示右单旋的具体步骤。

a. 先将 20 的左子树的根节点,也就是 14 旋转成为这棵树或者子树的根,将 20 变为 14 的右子树的根节点

b. 再将 14 的右子树变为 20 的左子树,也就是 B 变为 20 的左子树

按照上面的旋转步骤,旋转之后的树如图所示:

经过右单旋之后,树的高度由原来的 h+3 变为了旋转之后的 h+2,高度不仅变回了插入节点之前的高度,而且 14 和 20 的平衡因子也变为了 0。最重要的是,如果这棵树是一棵子树,其是不影响树中的其他部分的,而且整棵树的高度恢复成为了插入之前的高度,别的节点的平衡因子也不必改变,这样不仅完成了树平衡的调整,也完成了平衡因子的更新。

右单旋代码

cpp 复制代码
void RotateR(Node* parent)
{
	Node* Parent = parent->_parent;
	Node* subLR = parent->_left->_right;
	Node* subL = parent->_left;

	//不要忘记更改父亲的指向
	//parent 可能为根节点
	if (parent == _root)
		_root = subL;
	else
	{
		if (parent == Parent->_left)
			Parent->_left = subL;
		else
			Parent->_right = subL;
	}

	//subL 的父亲节点也需要更改
	subL->_parent = Parent;

	//更改左右孩子指向
	parent->_left = subLR;
	subL->_right = parent;

	//不要忘记更改 parent 以及 subLR 的父亲指向
	parent->_parent = subL;
	if (subLR)
		subLR->_parent = parent;

	//最后不要忘记更新平衡因子
	subL->_bf = parent->_bf = 0;
}

在写代码的时候,不要只顾着更改左孩子与右孩子指针指向。由于在 AVLNode 中还有 _parent 指针,所以我们也需要更改 _parent 指针指向,在右单旋过程中,一共需要更改的节点有三个,前两个就是上面图中的 20 与 14,还有一个就是 14 右子树中的根节点,也就是 B 的根节点,这些节点的 _parent 指针都需要改变。但是 B 可能为空树,所以我们需要先判断 B 根节点是否为空,再去更改 _parent 指向。

左单旋

左单旋与右单旋正好相反,但是大致逻辑是相同的,这里就只展示抽象图。

在右单旋中,是在高的左子树中再插入节点使之变得更高,会用右单旋;而在左单旋中,适合于在高的右子树插入节点,使右子树变得更高的情况。整棵子树的根节点平衡因子会由原来的 1 变为 2。

左单旋的旋转步骤

还是以上面那幅抽象图来说明左单旋的旋转步骤。

a. 先将 20 的右子树的根节点,也就是 28 旋转成为这棵树或者子树的根,将 20 变为 28 的左子树的根节点

b. 再将 28 的左子树变为 20 的右子树,也就是 B 变为 20 的右子树

旋转之后的树如图所示:

和右单旋一样,左单旋之后的树依然会恢复到之前树的高度,也就是 h+2;同时,20 与 28 的平衡因子也同步更新为 0。

左单旋代码

cpp 复制代码
void RotateL(Node* parent)
{
	Node* Parent = parent->_parent;
	Node* subR = parent->_right;
	Node* subRL = subR->_left;

	//不要忘记更改父亲的指向
	//parent 可能为根节点
	if (parent == _root)
		_root = subR;
	else
	{
		if (parent == Parent->_left)
			Parent->_left = subR;
		else
			Parent->_right = subR;
	}

	//subR 的父亲节点也需要更改
	subR->_parent = Parent;

	//更改孩子指向
	subR->_left = parent;
	parent->_right = subRL;

	//不要忘记更改 parent 与 subRL 的父亲指向
	parent->_parent = subR;
	if (subRL)
		subRL->_parent = parent;

	//更新平衡因子
	parent->_bf = subR->_bf = 0;
}

依然是不要忘记更改 _parent 指针的指向与更新平衡因子。

左右双旋

如果出现了以下场景,是会采用左右双旋的场景:

与右单旋类似,都是在高的左子树里面插入节点使得左子树变得更高;但是与右单旋不同的是,其并不是在其左子树的左子树里面插入,而是在其左子树的右子树里面插入,也就是不在 A 里面插入,而在 B 里面插入时,会采用左右双旋的方式来恢复平衡。

左右双旋的旋转步骤

根据名字来看,左右双旋具体的旋转步骤是先用一次左单旋,再用一次右单旋。具体的旋转步骤如下:

a. 先以 14 为父亲节点,进行一次左单旋,也就是 RotateL(14)

b. 再以 20 为父亲节点进行一次右单旋,也就是 RotateR(20)

但是呢,左右双旋具体又分为三种情况。但是这三种情况仅仅是平衡因子更新不同,旋转步骤都是相同的:

情况一

第一种情况是因为插入在了 16 的左子树从而导致 20 的平衡因子由 -1 变为了 -2,旋转之后的树如图。

经过旋转之后,原来的孙子节点 16 现在变成了整棵子树的根节点,平衡因子更新为了 0;原来的祖先节点 14 和 20 分别变成了 16 的左孩子和右孩子,平衡因子分别更新为了 0 和 1;同时整棵树的高度也变成了插入之前的高度 h+2,所以旋转之后也不必更新其他节点的平衡因子了。

情况二

情况二是因为在 16 的右子树中插入使得 20 的左子树变高。旋转之后的树如图所示。

情况二中 16 的平衡因子依旧为 0,但是 20 的平衡因子变为了 0,14 的平衡因子变为了 -1。

情况三

第三种情况比较特殊,就是在 14 为叶子节点,且 20 右子树为空的情况。此时在 14 的右子树插入节点,使得 20 的左子树变高。旋转之后的树如图所示。

旋转之后,三个节点的平衡因子都变为了 0。

左右双旋代码

cpp 复制代码
void RotateLR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;

	//先以 subL 为 parent 进行左单旋
	RotateL(subL);
	//再以 parent 为 parent 进行右单旋
	RotateR(parent);

	//根据三种情况进行平衡因子的更新
	//插入到 subLR 的左子树
	if (bf == -1)
	{
		subL->_bf = 0;
		parent->_bf = 1;
		subLR->_bf = 0;
	}
	//插入到 subLR 的右子树
	else if (bf == 1)
	{
		subL->_bf = -1;
		parent->_bf = 0;
		subLR->_bf = 0;
	}
	//subLR 为叶子节点
	else if (bf == 0)
	{
		subL->_bf = parent->_bf = 0;
	}
	else
	{
		//进行差错处理
		assert(false);
	}
}

在这里面就不需要更新 _parent 指针了,因为 RotateL 与 RotateR 函数会帮助我们更新的。

右左双旋

右左双旋与左右双旋恰好相反,与左单旋的情况类似,在下面这种情况下会进行右左双旋。

就是在原本更高的右子树里再插入节点,只不过不是在 28 的右子树中插入,而是在左子树中插入;即不是在 C 中插入,而是在 B 中插入,这时候就需要进行右左双旋。

右左双旋的旋转步骤

从名字来看,右左双旋呢就是先经历一次右单旋,再进行一次左单旋。以上面的抽象图来说步骤就是:

a. 先以 28 为父亲节点进行一次右单旋,也就是 RotateR(28)

b. 再以 20 为父亲节点进行一次左单旋,也就是 RotateL(20)

右左双旋呢,也分为三种情况,与左右双旋的三种情况类似。具体三种情况如图。

情况一

情况一是在 20 的右子树的 28 的左子树 25 的右子树中插入节点导致右子树高,进行右左双旋后的树如图所示。

旋转之后,25 与 28 的平衡因子变为了 0, 20 的平衡因子变为了 -1。

情况二

节点因为插入在了 20 的孙子节点 25 的左子树而导致右子树变高,进行右左双旋后的树如图所示。

此时平衡因子是 25 和 20 更新为了 0, 28 更新为了 1。

情况三

第三种情况就是插入的 25 为叶子节点的情况,此时右左双旋之后的树如图所示。

此时三个节点的平衡因子都更新到了 0。

右左双旋代码

cpp 复制代码
void RotateRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

	//先以 subR 为 parent 进行右单旋
	RotateR(subR);
	//再以 parent 为 parent 进行左单旋
	RotateL(parent);

	//根据不同情况更新平衡因子
	if (bf == 0)
	{
		subR->_bf = parent->_bf = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = -1;
		subR->_bf = 0;
		subRL->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

上面就是旋转的四种情况,尤其是双旋,比较复杂,但是理清了逻辑之后还是很好实现的。


3) AVLTree 的查找

查找的话就很简单了,跟 BST 的查找逻辑相同,这里就不再赘述。只不过对于 AVL 树来说,其查找效率是 O(logN) 的,效率更高。

查找代码

cpp 复制代码
Node* Find(const K& key)
{
	Node* cur = _root;
	
	while (cur)
	{
		if (key > cur->_data.first)
			cur = cur->_right;
		else if (key < cur->_data.first)
			cur = cur->_left;
		else
			return cur;
	}

	return nullptr;
}

3 AVLTree 的检测

由于 AVL 树的结构比较复杂,所以我们这里可以采用一定的算法来检测当前这棵树是否是 AVL 树,这里我们采用检测每个节点平衡因子的方法来检测是否是一棵 AVL 树。

既然检测平衡因子,首先我们先需要一个计算二叉树高度的函数,之后我们计算出左子树的高度与右子树的高度,用右子树高度减去左子树高度就得到了当前节点的平衡因子,然后我们检测平衡因子的绝对值是否大于等于 2,如果是,那就说明高度出问题了;如果平衡因子与当前节点的平衡因子不相等,那就说明是平衡因子出问题了。

计算二叉树高度

这个函数我们在二叉树时实现过,首先如果是空树,那高度肯定为0;然后我们递归的计算左子树高度,再递归的计算右子树高度;最后返回的是左右子树高度中较大值 + 1。

代码

cpp 复制代码
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;
}

检测是否平衡

这个函数也很简单。首先,如果是一棵空树,那肯定是一棵 AVL 树;然后计算出左子树高度,再计算出右子树高度,用右子树高度减去左子树高度得到平衡因子,然后判断平衡因子绝对值是否大于等于 2 或者是否与当前节点平衡因子相等;最后再递归的检查左子树与右子树是否是二叉平衡树就可以了。

代码

cpp 复制代码
bool _IsBalance(Node* root)
{
	//空树也是一棵AVL树
	if (root == nullptr)
		return true;
	int LeftHeight = Height(root->_left);
	int RightHeight = Height(root->_right);

	int dif = RightHeight - LeftHeight;

	if (dif >= 2 || dif <= -2)
	{
		std::cout << root->_data.first << "高度差异常" << std::endl;
		return false;
	}
	if (dif != root->_bf)
	{
		std::cout << root->_data.first << "平衡因子异常" << std::endl;
		return false;
	}

	//如果左边与右边都是AVL树,那就是一棵AVL树
	return _IsBalance(root->_left) && _IsBalance(root->_right);
}

4 总结

在这里给出 AVLTree 的所有实现代码:

cpp 复制代码
//AVLTree.hpp
#pragma once

#include <iostream>
#include <assert.h>

using namespace std;

template<class K, class V>
struct AVLNode
{
	pair<K, V> _data;
	AVLNode<K, V>* _left;
	AVLNode<K, V>* _right;
	AVLNode<K, V>* _parent;
	int _bf;

	AVLNode(const pair<K, V>& data)
		:_data(data)
		,_left(nullptr)
		, _right(nullptr)
		,_parent(nullptr)
		,_bf(0)
	{}
};

template<class K, class V>
class AVLTree
{
	typedef AVLNode<K, V> Node;

	void RotateR(Node* parent)
	{
		Node* Parent = parent->_parent;
		Node* subLR = parent->_left->_right;
		Node* subL = parent->_left;

		//不要忘记更改父亲的指向
		//parent 可能为根节点
		if (parent == _root)
			_root = subL;
		else
		{
			if (parent == Parent->_left)
				Parent->_left = subL;
			else
				Parent->_right = subL;
		}

		//subL 的父亲节点也需要更改
		subL->_parent = Parent;

		//更改左右孩子指向
		parent->_left = subLR;
		subL->_right = parent;

		//不要忘记更改 parent 以及 subLR 的父亲指向
		parent->_parent = subL;
		if (subLR)
			subLR->_parent = parent;

		//最后不要忘记更新平衡因子
		subL->_bf = parent->_bf = 0;
	}

	void RotateL(Node* parent)
	{
		Node* Parent = parent->_parent;
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		//不要忘记更改父亲的指向
		//parent 可能为根节点
		if (parent == _root)
			_root = subR;
		else
		{
			if (parent == Parent->_left)
				Parent->_left = subR;
			else
				Parent->_right = subR;
		}

		//subR 的父亲节点也需要更改
		subR->_parent = Parent;

		//更改孩子指向
		subR->_left = parent;
		parent->_right = subRL;

		//不要忘记更改 parent 与 subRL 的父亲指向
		parent->_parent = subR;
		if (subRL)
			subRL->_parent = parent;

		//更新平衡因子
		parent->_bf = subR->_bf = 0;
	}

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

		//先以 subL 为 parent 进行左单旋
		RotateL(subL);
		//再以 parent 为 parent 进行右单旋
		RotateR(parent);

		//根据三种情况进行平衡因子的更新
		//插入到 subLR 的左子树
		if (bf == -1)
		{
			subL->_bf = 0;
			parent->_bf = 1;
			subLR->_bf = 0;
		}
		//插入到 subLR 的右子树
		else if (bf == 1)
		{
			subL->_bf = -1;
			parent->_bf = 0;
			subLR->_bf = 0;
		}
		//subLR 为叶子节点
		else if (bf == 0)
		{
			subL->_bf = parent->_bf = 0;
		}
		else
		{
			//进行差错处理
			assert(false);
		}
	}

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

		//先以 subR 为 parent 进行右单旋
		RotateR(subR);
		//再以 parent 为 parent 进行左单旋
		RotateL(parent);

		//根据不同情况更新平衡因子
		if (bf == 0)
		{
			subR->_bf = parent->_bf = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	int _Height(Node* root) const
	{
		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) const
	{
		//空树也是一棵AVL树
		if (root == nullptr)
			return true;
		int LeftHeight = _Height(root->_left);
		int RightHeight = _Height(root->_right);

		int dif = RightHeight - LeftHeight;

		if (dif >= 2 || dif <= -2)
		{
			std::cout << root->_data.first << "高度差异常" << std::endl;
			return false;
		}
		if (dif != root->_bf)
		{
			std::cout << root->_data.first << "平衡因子异常" << std::endl;
			return false;
		}

		//如果左边与右边都是AVL树,那就是一棵AVL树
		return _IsBalance(root->_left) && _IsBalance(root->_right);
	}

	size_t _size(Node* root) const
	{
		//利用递归来算节点个数
		if (root == nullptr)
			return 0;

		return _size(root->_left) + _size(root->_right) + 1;
	}

public:
	bool Insert(const pair<K, V>& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			return true;
		}

		Node* cur = _root, *parent = nullptr;
		while (cur)
		{
			if (data.first > cur->_data.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (data.first < cur->_data.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
				return false;
		}

		//找到了该插入的位置
		cur = new Node(data);
		//更改parent 指向
		if (data.first > parent->_data.first)
			parent->_right = cur;
		else
			parent->_left = cur;

		//不要忘记更改 cur 的 _parent 指针
		cur->_parent = parent;

		//更新平衡因子
		while (parent)
		{
			if (cur == parent->_left)
				--parent->_bf;
			else
				++parent->_bf;

			//根据不同情况来判断是否向上更新
			if (parent->_bf == 0)
				//不更新了,直接退出
				break;
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//继续向上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				//旋转处理
				if (parent->_bf == -2)
				{
					Node* subL = parent->_left;
					if (subL->_bf == -1)
					{
						//右单旋
						RotateR(parent);
					}
					else if (subL->_bf == 1)
					{
						//左右双旋
						RotateLR(parent);
					}
					else
					{
						assert(false);
					}
				}
				else
				{
					Node* subR = parent->_right;
					if (subR->_bf == 1)
					{
						//左单旋
						RotateL(parent);
					}
					else if (subR->_bf == -1)
					{
						//右左双旋
						RotateRL(parent);
					}
					else
					{
						assert(false);
					}
				}

				//结束之后,也不需要更新了
				break;
			}
			else
			{
				//为了处理平衡因子出错的情况
				assert(false);
			}
		}

		return true;
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		
		while (cur)
		{
			if (key > cur->_data.first)
				cur = cur->_right;
			else if (key < cur->_data.first)
				cur = cur->_left;
			else
				return cur;
		}

		return nullptr;
	}

	size_t Height() const
	{
		return _Height(_root);
	}

	bool IsBalance() const
	{
		return _IsBalance(_root);
	}

	size_t size() const
	{
		return _size(_root);
	}

private:
	Node* _root = nullptr;
};


//testAVLTree.cpp
#include "AVLTree.hpp"
#include <vector>

int main()
{
	//这里直接采用随机值测试
	const int N = 100000;
	vector<int> v;
	v.reserve(N);
	srand(time(NULL));

	for (size_t i = 0; i < N; i++)
	{
		v.push_back(rand() + i);
	}

	//测试性能
	size_t begin1 = clock();
	AVLTree<int, int> t;
	for (auto& e : v)
	{
		t.Insert({ e, e });
	}
	size_t end1 = clock();

	cout << "Insert: " << end1 - begin1 << endl;
	cout << "is balance: " << t.IsBalance() << endl;

	cout << "Height: " << t.Height() << endl;
	cout << "Size: " << t.size() << endl;

	//查找
	size_t begin2 = clock();
	for (size_t i = 0; i < N; i++)
	{
		t.Find(rand() + i);
	}
	size_t end2 = clock();
	cout << "Find: " << end2 - begin2 << endl;

	return 0;
}

这篇文章主要是讲解第一个二叉平衡树 -- AVL 树的实现,其中最难的就是如何旋转来实现树的平衡。旋转共包括 4 种旋转:右单旋、左单旋、左右双旋、右左双旋,双旋中又各有三种不同的旋转场景。

本篇文章并没有讲解删除节点,如果大家有兴趣可以自己学习之后实现一下。

相关推荐
今儿敲了吗16 小时前
C++概述
c++·笔记
冰冰菜的扣jio16 小时前
Redis基础数据结构
数据结构·数据库·redis
C+-C资深大佬16 小时前
C++逻辑运算
开发语言·c++·算法
阿华hhh16 小时前
项目(购物商城)
linux·服务器·c语言·c++
Qhumaing16 小时前
C++学习:【PTA】数据结构 7-2 实验6-2(图-邻接表)
数据结构·c++·学习
方便面不加香菜16 小时前
基于顺序表实现通讯录项目
c语言·数据结构
꧁Q༒ོγ꧂17 小时前
算法详解(三)--递归与分治
开发语言·c++·算法·排序算法
李兴球17 小时前
这个来自五线城市的C++兴趣班的程序可不一般
c++
MQLYES17 小时前
03-BTC-数据结构
数据结构·算法·哈希算法