C++ AVL树

目录

一、AVL树介绍 

二、AVL树的树节点定义

三、AVL树的插入

1.插入

2.更新平衡因子

3.AVL树的旋转

3.1左旋 

3.2右旋

3.3左右双旋

3.4右左双旋

四、中序遍历

五、判断平衡

六、AVL树的删除


一、AVL树介绍 

在之前,我们已经学习过搜索二叉树了,但是普通的搜索二叉树有他的局限性,在往树中插入的元素有序或者接近有序情况下搜索效率会是O(n)

C++ AVL树_第1张图片

如上图,搜索的效率会很低下,因此,两位俄罗斯的数学家G.M.Adelson-Velskii 和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。这种方法以他们两个的名字命名,叫做AVLTree

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

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

C++ AVL树_第2张图片

对于这样一棵树,他的搜索效率就会很高,如果它有n个结点,其高度可保持在 O(log2n),搜索时间复杂度 O(log2n)。

二、AVL树的树节点定义

我们选择了(key,value)模型,方便数据的操作。跟之前不一样的地方在于AVL树多了一个父节点parent和一个平衡因子bf,有了父节点和平衡因子,我们在插入的时候才能更好的调节树的形状和高度。当平衡因子为0时,代表当前结点是平衡的,为1时,代表当前结点右树比左树的高度高1,为-1时,代表当前节点左树必右树高度高1,以此类推。

template 
struct AVLTreeNode
{
	AVLTreeNode* _parent = nullptr;
	AVLTreeNode* _left = nullptr;
	AVLTreeNode* _right = nullptr;
	pair _kv;
	int _bf;
	AVLTreeNode(const pair& kv)
		:_kv(kv)
		,_bf(0)
	{}
};

三、AVL树的插入

1.插入

AVL树的插入部分还是跟之前二叉搜索树十分类似,唯一的区别就是AVL树多了父节点,找到该插入的位置后,将该节点的指针指向父节点即可

template 
class AVLTree
{
	typedef AVLTreeNode Node;
public:
	bool Insert(const pair& 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->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		cur->_parent = parent;
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
        //后续再进行平衡因子的调节
    }
private:
    Node* _root;
};

2.更新平衡因子

在上面的代码中,我们仅仅是插入了结点,我们现在还不清楚插入结点后,树会变成什么样子,平衡因子是否还平衡,现在我们要来更新平衡因子,判断树是否处于平衡状态。

这里也解答了为什么我们需要父节点的指针,因为插入后,我们需要往上更新平衡因子。

当前节点在左,就让父亲的平衡因子减去1,在右,就让父亲结点的平衡因子加上1。再判断父亲的平衡因子的大小。

1.父亲的平衡因子==0,代表父亲节点已经平衡了,这种是最简单的,不需要调整,直接退出就好(因为平衡因子变为0,证明当前树的高度没有变,也就不会影响上面的树结点)

C++ AVL树_第3张图片C++ AVL树_第4张图片

2.父亲的平衡因子==1或者-1,代表当前结点的高度发生了变化,需要再往上面进行更新。直到平衡因子变为0或者(2和-2)

C++ AVL树_第5张图片C++ AVL树_第6张图片

3.父亲的平衡因子==2或者-2,代表当前结点左右子树高度差了2,已经不满足AVL树的定义了(左右子树高度差最多为1),需要旋转处理。(在下一小节讲解)

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)
    {
        //不平衡了,需要旋转
    }
}

3.AVL树的旋转

当前AVL树已经不平衡了,需要通过旋转让他保持平衡

else if(parent->_bf == 2 || parent->_bf == -2)

3.1左旋 

先来看一下下图这种情况(h高度为>=0的树),插入结点后发现树不再平衡,我们该如何通过旋转来解决呢?

 C++ AVL树_第7张图片

这里可以通过左旋处理(将左边高的地方旋转成低的地方),30的右节点指向60的左节点,让60的左节点指向30,这样一来树的高度就降低了1。(之前是h+1+1+1,现在是h+1+1) 同时,我们还可以发现,当前树的结点左右高度也相同了,60和30结点的平衡因子都变成了0。从不平衡旋转成了平衡。

C++ AVL树_第8张图片

 左旋代码如下,对比上图subR为60,subRLeft为60的左节点。为防止h为空树,因此要当subRLeft不为空时,才能将他的父亲指向父节点(也就是上图中的30)。同时我们也得注意所传递的parent并不一定是当前树的根,因此还得判断grandParent为父节点的父亲,通过grandParent来知道当前节点是父节点的左边还是右,再链接起来。最后旋转完成了,就将subR和parent的平衡因子_bf置为0。(因为已经平衡了)

void RotateL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRLeft = subR->_left;
	parent->_right = subRLeft;
    //防止subRLeft为空
	if(subRLeft)
		subRLeft->_parent = parent;

	Node* grandParent = parent->_parent;
	subR->_left = parent;
	parent->_parent = subR;
	if (parent == _root)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		if (grandParent->_left == parent)
		{
			grandParent->_left = subR;
		}
		else
		{
			grandParent->_right = subR;
		}
		subR->_parent = grandParent;
	}
	subR->_bf = parent->_bf = 0;
}
if (parent->_bf == 2 && cur->_bf == 1)
{
	RotateL(parent);
}

3.2右旋

跟左旋同理,下图需要右旋,代码也十分类似,就不多赘述了。

C++ AVL树_第9张图片

void RotateR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLRight = subL->_right;
	parent->_left = subLRight;
	//防止subLRight为空
	if (subLRight)
		subLRight->_parent = parent;

	Node* grandParent = parent->_parent;
	subL->_right = parent;
	parent->_parent = subL;
	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (grandParent->_left == parent)
		{
			grandParent->_left = subL;
		}
		else
		{
			grandParent->_right = subL;
		}
		subL->_parent = grandParent;
	}
	subL->_bf = parent->_bf = 0;
}
else if (parent->_bf == -2 && cur->_bf == -1)
{
	RotateR(parent);
}

3.3左右双旋

对于下图这种情况,在b或者c处添加节点,这变成了一个折线,不同于之前单旋的直线,如果仅仅是一个旋转,是解决不了问题的,我们可以尝试将他化简成之前我们熟悉的直线,那么仅仅需要旋转一次,就可以变成之前的直线了,那我们再次旋转一下,就可以完成折线的平衡了。

我们首先用30结点进行左旋,完成后再用90结点进行右旋,这相当于把60当成当前树的根节点,30在左,90在右。需要注意的是会有某个结点的平衡因子不为0,我们要根据情况修改,下图在b出添加结点,这个情况90的平衡因子为1。

C++ AVL树_第10张图片

而如果在c处添加结点,这个情况30的平衡因子为-1。

C++ AVL树_第11张图片

可以先通过代码复用,先以subL左旋旋,再以parent右旋,同时判断subLRight的平衡因子,如果为0,证明插入的结点就是subLRight,直接返回即可,是1或者-1,就可以知道我们是在subLRight左边插入的还是在右边插入的,根据情况修改即可。 

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

	RotateL(subL);
	RotateR(parent);
	if (bf == 0)
	{
		return;
	}
	else if (bf == -1)
	{
		parent->_bf = 1;
	}
	else if (bf == 1)
	{
		subL->_bf = -1;
	}
	else
	{
		assert(false);
	}
}
else if (parent->_bf == -2 && cur->_bf == 1)
{
	RotateLR(parent);
}

3.4右左双旋

右左双旋也和左右双旋类似,不多赘述,直接上图上代码。

C++ AVL树_第12张图片

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

	RotateR(subR);
	RotateL(parent);
	if (bf == 0)
	{
		return;
	}
	else if(bf == -1)
	{
		subR->_bf = 1;
	}
	else if(bf == 1)
	{
		parent->_bf = -1;
	}
	else
	{
		assert(false);
	}
}
else if (parent->_bf == 2 && cur->_bf == -1)
{
	RotateRL(parent);
}

如此一来,我们可以根据各种情况,去进行旋转和平衡因子的调节,就可以变成平衡搜索树了,最后要注意,发生旋转后需要break推出循环,因为已经平衡了,父节点的平衡因子都变成了0,不需要再往上继续更新平衡因子

四、中序遍历

中序遍历是老样子,在之前搜索二叉树已经讲解过了,直接上代码。

public:
void InOrder()
{
	_InOrder(_root);
	cout << endl;
}
private:
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;
	_InOrder(root->_left);
	cout << root->_kv.first << " " << root->_kv.second << endl;
	_InOrder(root->_right);
}

代码测试一下 

#include
using namespace std;
#include"AVLTree.h"
#include 

int main()
{

	int n = 30;
	vector v;
	v.reserve(n);
	//srand(time(0));
	for (int i = 0; i < n; i++)
	{
		v.push_back(rand());
	}
	AVLTree t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
	}
	t.InOrder();

}

 没毛病

C++ AVL树_第13张图片

五、判断平衡

刚刚的中序遍历只能告诉我们,他是一个搜索二叉树,但是我们并不知道他是否平衡,因此我还可以写代码来验证一下。

这里我们可以通过树的高度来进行验证,只要左树的高度跟右树的高度差小于1,就证明是平衡二叉树。代码如下

public:
bool IsBalance()
{
	return _IsBalance(_root);
}
private:
//求二叉树的高度
int _Height(Node* root)
{
	if (root == nullptr)
		return 0;
	int leftHegiht = _Height(root->_left);
	int rightHegiht = _Height(root->_right);
	return 1 + (leftHegiht > rightHegiht ? leftHegiht : rightHegiht);
}
//判断平衡
bool _IsBalance(Node* root)
{
	if (root == nullptr)
		return true;
	int leftHeight = _Height(root->_left);
	int rightHegiht = _Height(root->_right);
	if (rightHegiht - leftHeight != root->_bf)
	{
		cout << root->_kv.first <<"结点的平衡因子异常" << endl;
		return false;
	}
	return abs(leftHeight - rightHegiht) < 2
		&& _IsBalance(root->_left)
		&& _IsBalance(root->_right);
}

 测试代码

int main()
{
	int n = 1000;
	vector v;
	v.reserve(n);
	srand(time(0));
	for (int i = 0; i < n; i++)
	{
		v.push_back(rand());
	}
	AVLTree t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
		cout << "插入:" << e << "->" << t.IsBalance() << endl;
	}
	t.InOrder();
	cout << t.IsBalance() << endl;
}

​​​​​​​C++ AVL树_第14张图片

六、AVL树的删除

AVL树的相当复杂,我们了解即可

如下,删除左右子树都为空的结点(叶子结点)

C++ AVL树_第15张图片

C++ AVL树_第16张图片

如果删除节点后,往上更新时,有结点的平衡因子变成了2或者-2,就需要开始旋转。如果删除非叶子结点,就要像之前的搜索二叉树一样,找结点来代替,之后再更新平衡因子,再判断是否旋转。删除我们了解即可!

最后附上总代码  AVLTree.h

#pragma once
#include

template 
struct AVLTreeNode
{
	AVLTreeNode* _parent = nullptr;
	AVLTreeNode* _left = nullptr;
	AVLTreeNode* _right = nullptr;
	pair _kv;
	int _bf;
	AVLTreeNode(const pair& kv)
		:_kv(kv)
		,_bf(0)
	{}
};

template 
class AVLTree
{
	typedef AVLTreeNode Node;
public:
	bool Insert(const pair& 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->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		cur->_parent = parent;
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		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)//平衡因子变为2或者-2,需要旋转
			{
				if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				break;
			}
			else
			{
				assert(false);
			}
		}
		return true;
	}

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

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

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLRight = subL->_right;
		parent->_left = subLRight;
		//防止subLRight为空
		if (subLRight)
			subLRight->_parent = parent;

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

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

		RotateR(subR);
		RotateL(parent);
		if (bf == 0)
		{
			return;
		}
		else if(bf == -1)
		{
			subR->_bf = 1;
		}
		else if(bf == 1)
		{
			parent->_bf = -1;
		}
		else
		{
			assert(false);
		}
	}
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLRight = subL->_right;
		int bf = subLRight->_bf;

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

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

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

private:
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		int leftHegiht = _Height(root->_left);
		int rightHegiht = _Height(root->_right);
		return 1 + (leftHegiht > rightHegiht ? leftHegiht : rightHegiht);
	}
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;
		int leftHeight = _Height(root->_left);
		int rightHegiht = _Height(root->_right);
		if (rightHegiht - leftHeight != root->_bf)
		{
			cout << root->_kv.first <<"结点的平衡因子异常" << endl;
			return false;
		}
		return abs(leftHeight - rightHegiht) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_kv.first << " " << root->_kv.second << endl;
		_InOrder(root->_right);
	}
	Node* _root = nullptr;
};

test.cpp 

#include

using namespace std;
#include"AVLTree.h"
#include 
int main()
{
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	//AVLTree avlt;
	//for (auto e : a)
	//{
	//	avlt.Insert(make_pair(e,e));
	//}
	//avlt.InOrder();
	//cout< v;
	//v.reserve(n);
	srand(time(0));
	//for (int i = 0; i < n; i++)
	//{
	//	v.push_back(rand());
	//}
	//AVLTree t;
	//for (auto e : v)
	//{
	//	t.Insert(make_pair(e, e));
	//}
	//t.InOrder();

	int n = 1000;
	vector v;
	v.reserve(n);
	srand(time(0));
	for (int i = 0; i < n; i++)
	{
		v.push_back(rand());
	}
	AVLTree t;
	for (auto e : v)
	{
		t.Insert(make_pair(e, e));
		cout << "插入:" << e << "->" << t.IsBalance() << endl;
	}
	t.InOrder();
	cout << t.IsBalance() << endl;
}

感谢大家观看!!!

你可能感兴趣的:(数据结构,c++)