AVL树C++实现——高度平衡二叉搜索树

本文详细介绍了AVL树的概念、节点定义、插入操作及其平衡因子的更新规则。通过左单旋、右单旋以及左右、右左右旋转调整来保持树的平衡。并提供了测试案例以验证AVL树的正确性和平衡性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、AVL树的概念

二、AVL 树节点的定义

三、AVL 树的插入

3.1 搜索树的插入

3.2 更新平衡因子的规则

3.4 左单旋调整

3.3 右单旋调整

3.5 先左单旋再右单旋

1. 右左插入

2. 右右插入

3. 右侧新增

3.5 先右单旋再左单旋

四、AVL 树测试

五、代码与测试用例


一、AVL树的概念

二叉搜索树可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于再顺序表中搜索元素,效率低下。

因此,两位俄罗斯数学家便发明了一种解决上述问题的方法:

当向二叉搜索树中插入新节点后,如果能保证每个结点左右子树高度之差的绝对值不超过1(需要对树的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一颗AVL树或者是空树,或者是具有以下性质的二叉搜索树:

  • 她的左右子树都是AVL树
  • 左右子树高度之差(简称平衡因子)的绝对值不超过1(-1 / 0 /1 )

如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有 n 个结点,其高度可保持在 logN,搜索的时间复杂度为 logN。

二、AVL 树节点的定义

二叉搜索树结点的定义与 AVL 树结点的定义在于:

  1. 三叉链模式,增加了父指针。
  2. 增加了平衡因子,方便我们调整平衡(可有可无,只是为了方便我们实现)

其数据的存储方式我们采用 K-value 模型。

三、AVL 树的插入

3.1 搜索树的插入

AVL 本质就是二叉搜索树,只不过 AVL 树是在二叉搜索树的基础上引入了平衡因子。即,在 AVL 树中的插入过程可以分为两步:

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

我们先来实现第一步,按二叉搜索树的方式插入新节点。

 这里就不过多介绍了,上一篇讲解过了二叉搜索树的插入。二叉搜索树的插入

bool _Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}
	Node* parent = nullptr;
	Node* cur = _root;

	//找插入的位置
	while (cur)
	{
		if (cur->_kv.first < kv.first)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_kv.first > kv.first)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	//开始插入
	cur = new Node(kv);
	if (parent->_kv.first < kv.first)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	//因为增加了父域,所以还要处理cur的父域
	cur->_parent = parent;
}

3.2 更新平衡因子的规则

以上我们完成了节点的插入,但是插入了节点后,树可能会出现以下情况:

 所以,为了防止以上情况出现,我们在插入后要调整平衡。

为了维持平衡,我们引入了平衡因子来控制树的平衡,所以我们每次插入后要更新平衡因子,如果出现失去平衡的情况,我们则要进行旋转来维持平衡。

更新平衡因子的规则:

  1. 新增在右边,则会让父平衡因子++,新增在左边,父平衡因子 -- ;
  2. 更新后,如果 parent->bf == 0,说明 parent 插入前的平衡因子是 1 or -1,插入填上了矮的一边,parent 的子树高度不变,不需要继续往上更新
  3. 更新后,如果 parent->bf 为 1 或 -1, 说明parent插入前的平衡因子是0,说明左右子树高度相等,插入后有一边高,parnet 高度变了,需要继续往上更新
  4. 更新后,如果 parent->bf == 2 或 -2 ,说明 parent 插入前的平衡因子是 1 or -1,已经到达平衡临界值,parent 子树进行旋转处理将树保持平衡
  5. 更新后,如果 parent->bf >2 或 <-2 ,则说明插入前树已经失去的平衡,要进行代码的检查。

现在,我们要对祖先路径更新平衡因子,直到更新到根节点结束。

注释我写的非常清楚,结合上图和代码一起理解。

 当规则4出现时,我们则要进行旋转来调整平衡,旋转共分为 4 种情况,左左右单旋、右右左单旋、左右左右双旋、右左右左双旋我们一一进行解决。

3.4 左单旋调整

新节点插入较高右子树的右侧 --- 右右:左单旋
观察下图,如果我们在 c 节点下插入新节点,导致 60的左右子树失去平衡,调整前后的情况如下:

 现在我们要发现左单旋的执行规律:

1.树的右子树失去平衡(parent->bf == 2),并且新插入的节点位于右子树的右侧(cur->bf == 1)。

2. 将 cur->left 交由 parent->right 抚养,再调整父子关系,使 cur->left 指向 parent ,即完成旋转。

3.平衡因子归零。观察上图,旋转后,60 的左右子树高度相同(a、b高度为h,c的高度为 h+1,注意:60左子树的高度也为h+1,因为存在 30 节点)

接下来我们编写代码

3.3 右单旋调整

新节点插入较高左子树的左侧-左左:右单旋。
观察下图,如果我们在 a 节点处插入新节点,导致 60的左右子树失去平衡,调整前后的情况如下:

 现在我们要发现右单旋的执行规律:

1.树的左子树失去平衡(parent->bf == -2),并且新插入的节点位于左子树的左侧(cur->bf == -1)。

2. 将 cur->right 交由 parent->left 抚养,再调整父子关系,使 cur->right 指向 parent ,即完成旋转。

右单旋就是方向相反的左单旋,理解了左单旋,右单旋实现起来非常简单, 代码如下。
//情况2:左左右单旋
void RotateR(Node * parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;

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

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

3.5 先左单旋再右单旋

上面我们解决了4种情况中的两种情况,对于平衡的调整还有左右左右双旋和右左右左双旋这两种情况。

其调整的条件是根的左子树的右侧插入了新的节点。

这种插入情况我们还要再细分为三种插入情况,其平衡因子会出现不同的情况:

  1. 在30的右子树 60 左侧进行插入。
  2. 在30的右子树 60 右侧进行插入。
  3. 30的右子树为空,插入的节点为60本身。

1. 右左插入

先分析第一种情况,如图:

此时 60 节点的左侧插入节点导致 90 节点下的左右子树高度失衡,所以我们接下来要对 90 节点下进行旋转处理。

旋转的步骤分为两步:

 先对30进行左单旋,然后再对90进行右单旋

1.先对 30 进行左单旋,这样,30节点的左右子树便恢复了平衡。

2.因为 60 左子树高度高于右子树,以 90节点视角来看,符合右单旋的条件(左高右低),所以90节点进行右单旋

然后对 90 进行右单旋

2. 右右插入

 以上情况是在 60 的左边进行插入,同样也有在 60 的右边进行插入,两种插入会导致 90、30的 平衡因子出现不同的情况。注意观察下图平衡因子的变化,与上图是不同的。

 然后对90进行右单旋

3. 右侧新增

 此时我们解决的都是 30 有左右子树,60有左右子树,还有一种可能,h为0,即60本身为新增。

然后进行旋转

 好的,三种情况我们都分析完了,发现其实无论哪种情况,其步骤都经历的左单旋和右单旋,只是平衡因子因情况的不同而不同。

而这三种情况的不同都是由 60 影响的,所以我们根据 60 平衡因子的不同分别进行调整即可。

 

3.5 先右单旋再左单旋

 同样的,右左右左双旋和左右左右双旋几乎一致,就不做过多讲解了,结合画图,仿照左右双旋,很容易写出代码。

 代码如下:

else if (parent->_bf == 2 && cur->_bf == -1)
{
	//右左双旋
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;

	//旋转
	RotateR(parent->_right);
	RotateL(parent);

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

四、AVL 树测试

实现了插入我们来测试一下代码。

 好的,程序能正常运行,但程序能正常运行就说明我们的 AVL 就是成功的吗?我们的 AVL 树就是平衡的吗?

所以现在我们要使用检查树的高度的方式来检查树是否是平衡的。即,如果左右子树的高度差不超过1,则说明该树是平衡的,接下来我们来编写成员函数。

int _Height(Node* root)
{
	if (root == nullptr)
		return 0;
	return max(_Height(root->_left), _Height(root->_right)) + 1;
}

有了测量高度的函数后我们来编写平衡判断函数。

bool _IsBalance(Node* root)
{
	if (root == nullptr)
		return true;
	int leftHeight = _Height(root->_left);
	int rightHeight = _Height(root->_right);
	int diffHeight = abs(leftHeight - rightHeight);

	// |diffHeight|小于2 则当前树平衡。
	// 还要去判断左右子树是否平衡
	return abs(diffHeight) < 2
		&& _IsBalance(root->_left)
		&& _IsBalance(root->_right);
}

好的,接下来往树中插入一万个随机数据,然后来判断其是否平衡。

14层的平衡二叉树大约有16万个节点,所以高度计算正确,通过 IsBalance 函数也判定当前树为平衡。

最后我们来玩一下AVL树,往树中插入一亿个数据,然后找到77777处的数据。结果如下:

 一亿个数据,插入的效率是O(n*logN),想找到其中任意一个值,最多查找高度处。

五、代码与测试用例

#include <iostream>
#include <map>
#include <utility>
#include <assert.h>
#pragma once
using namespace std;
//AVL树结点定义
template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode <K, V>* _left;
	AVLTreeNode <K, V>* _right;
	AVLTreeNode <K, V>* _parent;

	pair<K, V> _kv;   //key-value模型
	int _bf;          //banlance factor  平衡因子

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr), _right(nullptr), _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};

template<class K, class V>
struct AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			return true;
		}

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

		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		cur->_parent = parent;

		// 控制平衡
		// 1、更新平衡因子
		while (parent)
		{
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			if (parent->_bf == 0)
			{
				break;
			}
			else if (abs(parent->_bf) == 1)
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (abs(parent->_bf) == 2)
			{
				// 说明parent所在子树已经不平衡了,需要旋转处理
				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;
			}
			else
			{
				assert(false);
			}
		}

		return true;
	}
	void Inorder()
	{
		_Inorder(_root);
	}
	int Height()
	{
		return _Height(_root);
	}
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	pair<K, V> Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else
			{
				return cur->_kv;
			}
		}
		return pair<int, int>(0, 0);
	}
private:

	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		int diffHeight = abs(leftHeight - rightHeight);

		// |diffHeight|小于2 则当前树平衡。
		// 还要去判断左右子树是否平衡
		return abs(diffHeight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		return max(_Height(root->_left), _Height(root->_right)) + 1;
	}

	void _Inorder(Node* root)
	{
		if (root)
		{
			_Inorder(root->_left);
			cout << root->_kv.first << ":" << root->_kv.second << endl;
			_Inorder(root->_right);
		}
	}
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* ppNode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent)
			{
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}

			subR->_parent = ppNode;
		}

		subR->_bf = parent->_bf = 0;
	}

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

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

		Node* ppNode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

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

			subL->_parent = ppNode;
		}

		subL->_bf = parent->_bf = 0;
	}

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

		RotateL(parent->_left);
		RotateR(parent);

		subLR->_bf = 0;
		if (bf == 1)
		{
			parent->_bf = 0;
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			// 错的
			/*parent->_bf = 0;
			subL->_bf = 1;*/

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

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

		int bf = subRL->_bf;

		RotateR(parent->_right);
		RotateL(parent);

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

private:
	Node* _root = nullptr;
};

void TestAVLTree1()
{
	int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	AVLTree<int, int> t1;
	for (auto e : a)
	{
		t1.Insert(make_pair(e, e));
	}
	t1.Inorder();
	cout << t1.Height() << endl;
	if (t1.IsBalance())
		cout << "平衡树" << endl;
	else
		cout << "非平衡树" << endl;
}


void TestAVLTree2()
{
	//随机插入一万个数据
	size_t N = 10000;
	srand((unsigned int)time(NULL));
	AVLTree<int, int> t1;
	for (size_t i = 0; i < N; ++i)
	{
		int x = rand() % 100 + 1;
		t1.Insert(make_pair(i, x));
		bool IsBalance = t1.IsBalance();
		printf("%-4d:%-2d  ", i, x);
		printf("Balance:%d\n", IsBalance);
		if (!IsBalance)
		{
			cout << i << endl;
			assert(false);
		}
	}
	//树的高度
	cout << "Tree Height:" << t1.Height() << endl;

	//判断平衡
	if (t1.IsBalance())
		cout << "IS AVL" << endl;
	else
		cout << "NOT AVL" << endl;
}

void TestAVLTree3()
{
	size_t N = 100000000;
	srand((unsigned int)time(NULL));
	AVLTree<int, int> t1;
	for (size_t i = 0; i < N; ++i)
	{
		int x = rand() % 100 + 1;
		t1.Insert(make_pair(i, x));
	}
	cout << t1.Height() << endl;
	pair<int, int> result = t1.Find(777777);
	cout << result.first << ":" << result.second << endl;
}

int main()
{
	//TestAVLTree1();
	//TestAVLTree2();
	TestAVLTree3();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Brant_zero2022

素材免费分享不求打赏,只求关注

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值