C++实现AVL树

为什么要有AVL树

我们都知道二叉搜索树的规则,插入一个节点时,如果比当前节点值大就到右边,反之则到左边。这样使得中序遍历这颗树可以得到一个有序的数组。如果我们要查找这颗树当中的一个值,最大的时间复杂度是多少呢?O(N),发生这种事情的原因呢,如果我们插入一个有序的数据,它就会排成一条链。
二叉搜索树
如果树的结构为这种结构的话,我们要查找一个数就可能需要遍历这个数的所有节点。为了解决这个问题,两位俄国的数学家:G.M.Adelson-Velskii和E.M.Landis在1962年发明连里一种解决上述问题的方法:当二叉搜索树中插入新的节点后,如果能保证每个节点的左右子树高度差的绝对值不超过1,即可降低书的高度,从而减少平均搜索长度。

1.AVL树

AVL树是具有以下性质的二叉搜索树:

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

如果一颗二叉搜索树是高度平衡的。那么它就是AVL树。如果它右n个节点,其高度课保持再logn,搜索时间复杂度为logn

2.实现AVL树

2.1AVL树节点的定义

在二叉平衡树的基础上,添加了平衡因子_bf(左右子树高度之差),以及parent指针,用来指向父节点。

#include <iostream>
using namespace std;

template <class K,class V>
struct AVLTreeNode
{
	AVLTreeNode(const pair<K,V> kv)
		:_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_kv(kv),
		_bf(0)

	{}
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;
};

template <class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K,V> Node;
public:

private:
	Node* _root = nullptr;
};

2.2AVL树的插入

AVL树的插入可以说是AVL树最重要的内容,不过因为AVL树是再二叉平衡树的基础上加入了平衡因子,所以最开始的插入操作和二叉平衡树是相同的。在插入后就要更新平衡因子。
cur插入后,parent的平衡因子一定需要调整,在插入之前,parent
的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:

  1. 如果cur插入到parent的左侧,只需给parent的平衡因子-1即可
  2. 如果cur插入到parent的右侧,只需给parent的平衡因子+1即可

此时:parent的平衡因子可能有三种情况:0,正负1, 正负2

  1. 如果parent的平衡因子为0,说明插入之前parent的平衡因子为正负1,插入后被调整成0,此时满足
    AVL树的性质,插入成功
  2. 如果parent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1,此
    时以parent为根的树的高度增加,需要继续向上更新
bool Insert(const pair<K, V> kv)
	{
		//1.前期操作与二叉树搜索树的插入操作相似
		if (!_root)
		{
			_root = new Node(kv);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (kv.first > cur->_kv.first)
			{
				parent = cur;//更新父节点
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;//已存在
			}
		}
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;//链接父节点

		//2.新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树
		while (parent)
		{
			if (parent->_right == cur)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}
			if (parent->_bf == 0)
			{
				break;//更新后bf为0,表示对二叉树的平衡无影响,可以直接退出
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;//向上更新
			}
			else if (parent->_bf == -2 || parent->_bf == 2)
			{
				//出现问题了,要进行处理
			}
			else
			{
				assert(false);//表示早已出现问题,直接断错
			}
		}
		return true;
	}

以上就是AVL树中插入的基础结构,先进行的二叉搜索树中的插入操作,然后在节点插入过后,因为AVL树的平衡性可能会改变,所以我们要开始对树进行处理。下面就是针对当树的平衡性出现问题时,我们应该进行的操作。即旋转
也就是上面未提到的第3点
3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理

2.2.1.AVL树的旋转

如果在一颗原本平衡的AVL树中插入一个新的节点,造成了AVL树的不平衡。此时我们必须要调整树的结构,使之平衡化。根据插入位置的不同,AVL树的旋转可以分未4种:
1.新节点插入较高左子树的左侧——此时parent的平衡因子为-2,cur的平衡因子为-1
用图片表示:
右旋

右旋之后:原本parent和cur的平衡因子都变为0。

上图在插入前,AVL树是平衡的,新节点插入到30的左子树(注意:此处不是左孩子)中,30左子树增加
了一层,导致以60为根的二叉树不平衡,要让60平衡,只能将60左子树的高度减少一层,右子树增加一层,
即将左子树往上提,这样60转下来,因为60比30大,只能将其放在30的右子树,而如果30有右子树,右子树根的值一定大于30,小于60,只能将其放在60的左子树,旋转完成后,更新节点的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:
1. 30节点的右孩子可能存在,也可能不存在
2. 60可能是根节点,也可能是子树
如果是根节点,旋转完成后,要更新根节点
如果是子树,可能是某个节点的左子树,也可能是右子树

void RotateR(Node* parent)
	{
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;
		Node* ppnode = parent->_parent;//最后链接更新后的"头节点"
		parent->_left = SubLR;
		SubL->_right = parent;
		if (SubLR)
			SubLR->_parent = 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;
		}
		//更新bf
		parent->_bf = 0;
		SubL->_bf = 0;
	}

2.新节点插入较高右子树的右侧——parent的平衡因子为2,cur的平衡因子为1:左旋
具体的操作和上面并没有太多的差别,来看看图片的表示吧:
左旋

void RotateL(Node* parent)
	{
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;
		Node* ppnode = parent->_parent;
		parent->_right = SubRL;
		SubR->_left = parent;
		if (SubRL)
			SubRL->_parent = parent;
		parent->_parent = SubR;
		if (_root == parent)
		{
			_root = SubR;
			SubR->_parent = nullptr;
		}
		else
		{
			if (ppnode->_right == parent)
			{
				ppnode->_right = SubR;
			}
			else
			{
				ppnode->_left = SubR;
			}
			SubR->_parent = ppnode;
		}
		SubR->_bf = 0;
		parent->_bf = 0;
	}

3.新节点插入较高子树的右侧——左右:先左旋再右旋
这种情况比前两种的情况就要复杂了。仅仅旋转一次已经满足不了条件了
画的步骤比较多,从网络偷了一张图:
左右旋

void RotateLR(Node* parent)
	{
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;
		int bf = SubLR->_bf;
		RotateL(SubL);
		RotateR(parent);

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

4.新节点插入较高右子树的左侧—右左:先右单旋再左单旋

右旋左旋

void RotateRL(Node* parent)
	{
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;
		int bf = SubRL->_bf;
		RotateR(SubR);
		RotateL(parent);
		//更新bf
		if (bf == 1)
		{
			SubR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			SubR->_bf = 1;
		}
		else
		{
			parent->_bf = 0;
			SubR->_bf = 0;
		}
	}

完成各个旋转函数的实现,让我们再补起前面的代码吧

bool Insert(const pair<K, V> kv)
	{
		//1.前期操作与二叉树搜索树的插入操作相似
		if (!_root)
		{
			_root = new Node(kv);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (kv.first > cur->_kv.first)
			{
				parent = cur;//更新父节点
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;//已存在
			}
		}
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;//链接父节点

		//2.新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树
		while (parent)
		{
			if (parent->_right == cur)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}
			if (parent->_bf == 0)
			{
				break;//更新后bf为0,表示对二叉树的平衡无影响,可以直接退出
			}
			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 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else
				{
					RotateLR(parent);
				}
				break;//更新完毕
			}
			else
			{
				assert(false);//表示早已出现问题,直接断错
			}
		}
		return true;
	}

AVL树的验证

我们写下了这个程序,但是怎么证明我们写对了呢?总不能使用俺寻思之力吧。为此我们还要写一个验证AVL树的函数。
我们都知道,AVL树一定也是二叉搜索树,所以如果中序打印出来不是一个有序的数组那么一定出问题了。验证完二叉搜索树后就是验证其为AVL树。
1.验证其为二叉搜索树

中序遍历为有序序列

写一个中序遍历,看打印结果

int main()
{
	//test
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
	}
	t.inorder();
	return 0;
}

打印结果:1 2 3 4 5 6 7 14 15 16
没问题!
2。验证其为AVL树
节点的左右高度差的绝对值一定小于2,且左右高度差等于bf。

1.每个节点子树高度差的绝对值不超过1
2.节点的平衡因子计算是否正确

bool isbanlance()
	{
		return _isbanlance(_root);
	}
	private:
		bool _isbanlance(Node* root)
		{
			if (!root) return true;
			int hleft = _height(root->_left);
			int hright = _height(root->_right);
			if (abs(hleft - hright) > 1)
			{
				cout << root->_kv.first<<"不平衡!" << endl;
				return false;
			}
			if (hright - hleft != root->_bf)
			{
				cout << root->_kv.first<<"平衡因子异常!" << endl;
				return false;
			}
			return _isbanlance(root->_left) && _isbanlance(root->_right);

		}

test

int main()
{
	//test
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int, int> t;
	for (auto e : a)
	{
		t.Insert(make_pair(e, e));
		cout << e << "->" << t.isbanlance() << endl;
	}
	t.inorder();
	return 0;
}

打印结果:
4->1
2->1
6->1
1->1
3->1
5->1
15->1
7->1
16->1
14->1
1 2 3 4 5 6 7 14 15 16
目前看没有问题,但这也只是小范围的数据,接下来我们试试大数据的。
大数据插入
可以看出随机数的插入也是没有问题的,那么我们的AVL树可以说是没有问题的。

优化验证函数
在查找树的高度的时候就已经,对树进行了遍历,后续却还要再次遍历一遍树结构,造成了时间的浪费,那么我们是不是可以在查找高度的时候直接把高度带回来,后面也不判断了。

bool _IsBalance(Node* root, int& height)
	{
		if (root == nullptr)
		{
			height = 0;
			return true;
		}

		int leftHeight = 0, rightHeight = 0;
		if (!_IsBalance(root->_left, leftHeight)
			|| !_IsBalance(root->_right, rightHeight))
		{
			return false;
		}

		if (abs(rightHeight - leftHeight) >= 2)
		{
			cout << root->_kv.first << "不平衡" << endl;
			return false;
		}

		if (rightHeight - leftHeight != root->_pf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

		height = leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;

		return true;
	}

代码

#include <iostream>
#include <assert.h>
#include <cstdbool>
#include <vector>
#include <cmath>
#include<utility>
using namespace std;

template <class K,class V>
struct AVLTreeNode
{
	AVLTreeNode(const pair<K,V> kv)
		:_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_kv(kv),
		_bf(0)

	{}
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	int _bf;
};

template <class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K,V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		//1.前期操作与二叉树搜索树的插入操作相似
		if (!_root)
		{
			_root = new Node(kv);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (kv.first > cur->_kv.first)
			{
				parent = cur;//更新父节点
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;//已存在
			}
		}
		cur = new Node(kv);
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		cur->_parent = parent;//链接父节点

		//2.新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了AVL树
		while (parent)
		{
			if (parent->_right == cur)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}
			if (parent->_bf == 0)
			{
				break;//更新后bf为0,表示对二叉树的平衡无影响,可以直接退出
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				//cur = parent;
				cur = cur->_parent;
				parent = parent->_parent;//向上更新
			}
			else if (parent->_bf == -2 || parent->_bf == 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)
				{
					RotateRL(parent);
				}
				else
				{
					RotateLR(parent);
				}
				break;//更新完毕
			}
			else
			{
				assert(false);//表示早已出现问题,直接断错
			}
		}
		return true;
	}
	void RotateR(Node* parent)
	{
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;
		Node* ppnode = parent->_parent;//最后链接更新后的"头节点"
		parent->_left = SubLR;
		SubL->_right = parent;
		if (SubLR)
			SubLR->_parent = 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;
		}
		//更新bf
		parent->_bf = 0;
		SubL->_bf = 0;
	}
	void RotateL(Node* parent)
	{
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;
		Node* ppnode = parent->_parent;
		parent->_right = SubRL;
		SubR->_left = parent;
		if (SubRL)
			SubRL->_parent = parent;
		parent->_parent = SubR;
		if (_root == parent)
		{
			_root = SubR;
			SubR->_parent = nullptr;
		}
		else
		{
			if (ppnode->_right == parent)
			{
				ppnode->_right = SubR;
			}
			else
			{
				ppnode->_left = SubR;
			}
			SubR->_parent = ppnode;
		}
		SubR->_bf = 0;
		parent->_bf = 0;
	}
	void RotateRL(Node* parent)
	{
		Node* SubR = parent->_right;
		Node* SubRL = SubR->_left;
		int bf = SubRL->_bf;
		RotateR(SubR);
		RotateL(parent);
		//更新bf
		if (bf == 1)
		{
			SubR->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			SubR->_bf = 1;
		}
		else
		{
			parent->_bf = 0;
			SubR->_bf = 0;
		}
	}
	void RotateLR(Node* parent)
	{
		Node* SubL = parent->_left;
		Node* SubLR = SubL->_right;
		int bf = SubLR->_bf;
		RotateL(SubL);
		RotateR(parent);

		if (bf == -1)
		{
			SubLR->_bf = 0;
			SubL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			SubLR->_bf = 0;
			SubL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			SubLR->_bf = 0;
			SubL->_bf = 0;
			parent->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	void inorder()//中序遍历
	{
		_inorder(_root);
		cout << endl;
	}
	int height()
	{
		return _height(_root);
	}
	bool isbanlance()
	{
		return _isbanlance(_root);
	}
	private:
		bool _isbanlance(Node* root)
		{
			if (!root) return true;
			int hleft = _height(root->_left);
			int hright = _height(root->_right);
			if (abs(hleft - hright) > 1)
			{
				cout << root->_kv.first<<"不平衡!" << endl;
				return false;
			}
			if (hright - hleft != root->_bf)
			{
				cout << root->_kv.first<<"平衡因子异常!" << endl;
				return false;
			}
			return _isbanlance(root->_left) && _isbanlance(root->_right);

		}
	
	private:
		int _height(Node* root)
		{
			if (!root) return 0;
			int hleft = _height(root->_left);
			int hright = _height(root->_right);
			return max(hleft, hright) + 1;
		}
	private:
		void _inorder(Node* root)
		{
			if (!root)
			{
				return;
			}
			_inorder(root->_left);
			cout << root->_kv.first<<' ';
			_inorder(root->_right);
		}
private:
	Node* _root = nullptr;
};

测试代码

//test
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);
	srand(time(0));

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

	size_t begin2 = clock();
	AVLTree<int, int> t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
		//cout << "Insert:" << e << "->" << t.IsBalance() << endl;
	}
	size_t end2 = clock();
	cout << t.isbanlance();

讲的比较乱。写的也比较赶。大概会有错误的地方,前面写的代码,后面我可能改了,应该都修改了,但也可能会遗漏。文章写到比较杂,感谢你的浏览。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Yui_

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值