继二叉搜索树后的AVL树


继二叉搜索树后的AVL树


为解决二叉搜索树退化为单支树问题,在二叉搜索树上增加限制条件,即AVL树和红黑树,其中红黑树又是map/set关联式容器实现的底层结构。


前言

在map/multimap/set/multiset文档介绍中我们可以知道这几个容器有个共同点为:其底层都是按照二叉搜索树来实现的,但是二叉搜索树有其自身的缺陷,若往树中插入的元素有序或者接近有序,则二叉搜索树便会退化为单支树,其时间复杂度会退化成O(N),因此map、set等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

编译环境:VS2013


一、AVL树的概念

一棵AVL树:二叉搜索树+限制条件(其左右子树高度差绝对值不大于1)

1.空树

2.非空时,满足:

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

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

/AVL树节点定义
template<class T>
class AVLTreeNode
{
	AVLTreeNode<T>* _left;//节点左孩子
	AVLTreeNode<T>* _right;//节点右孩子
	AVLTreeNode<T>* _parent;//节点双亲
	T _data; //节点值域
	int _bf; //该节点的平衡因子

	AVLTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _bf(0)
	{}
};

二、AVL树的插入

树的左右子树高度差绝对值不大于1的二叉搜索树即为AVL树,故AVL树的节点插入第一步同二叉搜索树的节点插入,元素插入后原树的平衡因子可能遭到破坏,故我们需对树进行调整。

step1:按照二叉搜索树的方式进行节点的插入。
step2:调整节点的平衡因子。

而对树进行调整的过程,即对树进行旋转来调整其平衡因子。

//AVL树节点插入
	//1.按照二叉搜索树的方式插入新节点
	//2.调整节点的平衡因子(平衡因子规定为:右子树高度-左子树高度)
	bool Insert(const T& data)
	{
		if (_root == nullptr)
		{
			//新插入节点即为根节点
			_root = new Node(data);
			return true;
		}
		//1.按照二叉搜索树插入新节点方式往AVL树中插入新节点
		//a.查找带插入节点在树中是否存在
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (data < cur->_data)
				cur = cur->_left;
			else if (data>cur->_data)
				cur =cur->_right;
			else
				return false;
		}
		//b.走下来表示未找到,进行节点的插入
		cur = new Node(data);
		if (data < parent->_data)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		cur->_parent = parent;

		//节点插入成功后,其parent的子树高度改变,故需更新parent的平衡因子
		while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			/
			if (parent->_bf == 0)//说明节点在插入之前parent有一孩子,插入新节点并未影响整体的树的高度,只是影响parent的平衡因子
				break;
			else if (1==parent->_bf  || -1==parent->_bf )
			{
				//说明以parent为根的二叉树高度增加了一层,需要继续往上更新平衡因子
				cur = parent;
				parent = cur->_parent;
			}
			else //parent的平衡因子为2或-2
			{
				if (parent->_bf == 2)
				{
					//说明其右子树高
					if (cur->_bf == 1)
					{
						RotateLeft(parent);//右子树高且为较高右子树的右侧,故左单旋
					}
					else
					{
						//右子树较高且为较高右子树的左侧,故子树先右单旋再整体左单旋,即右左双旋
						RotateRL(parent);
						//等价于:先RotateRight(parent->_right)再RotateLeft(parent)
					}
				}
				else
				{
					//说明其左子树高
					if (cur->_bf == -1)
					{
						RotateRight(parent);//左子树较高且为较高左子树的左侧,故右单旋
					}
					else
					{
						//左子树较高且为较高左子树的右侧,故子树先左单旋再整体右单旋,即左右双旋
						RotateLR(parent);
						//等价于:先RotateLeft(parent->_left)再RotateRight(parent)
					}
				}
				//在元素插入之前以parent为根的二叉树高度
				//插入元素后进行树的旋转之后以parent为根的二叉树的高度一样
				//故无需继续向上更新
				break;
			}
		}
		return true;
	}

三、AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点后,可能破坏原树的平衡,此时必须调整树的结构,使之平衡化。而根据节点插入位置的不同,我们将AVL树的旋转分为四种:右单旋左单旋右左双旋(先右单旋再左单旋)左右双旋(先左单旋再右单旋)

而对于每种旋转都有其对应的场景,具体如下。

1.右单旋

场景:当新节点插入到较高左子树的左侧时。

首先,我们先来分别一下较高左子树和较高子树的左侧和右侧概念。
在这里插入图片描述
由上图我们可以看到,往树中插入元素3后,根结点的平衡因子为-2,导致树不平衡。
原因为:插入元素3后,元素4的左子树高度增加1,由此上推,元素5和7的左子树也都增加了1,左子树高度增加,树失衡。此场景下旋转调整主要思想即将左子树高度降低,右子树高度升高,以达到树的平衡。
在这里插入图片描述
当插入元素在元素4的右侧时,树的调整同插入元素3时。在这里插入图片描述
综上,我们可以看到,在向上述AVL树中插入元素3和元素4.5的处理方法一致。

注意:此处元素7为根节点,而节点7也可能为某一节点的左孩子或是右孩子 ,即元素7有双亲。若节点7有双亲,在右单旋修改元素指针指向时,节点7双亲的对应指针也应修改为指向节点5。

//右单旋
	void RotateRight(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		//进行①指针的指向修改
		parent->_left = subLR;
		//subLR可能为空
		if (subLR)
		{
			subLR->_parent = parent;
		}

		//进行②指针的指向修改
		subL->_right = parent;
		Node* pParent = parent->_parent;
		parent->_parent = subL;
		subL->_parent = pParent;

		//parent为根节点时,subR直接为新的根节点
		//parent不为根节点时,parent可能为pParent的左孩子或是右孩子
		if (pParent == nullptr)
		{
			_root = subL;
		}
		else
		{
			if (parent == pParent->_left)
			{
				pParent->_left = subL;
			}
			else
			{
				pParent->_right = subL;
			}
		}
		//将平衡因子进行订正更新
		parent->_bf = subL -> _bf = 0;
	}

2.左单旋

场景:当新节点插入到较高右子树的右侧时。
在这里插入图片描述在这里插入图片描述
综上,我们可以看到,在向上述AVL树中插入元素7和元素5.5的处理方法一致。

注意:同右单旋,此处元素3为根节点,而节点3也可能为某一节点的左孩子或是右孩子 ,即元素3有双亲。若节点3有双亲,在左单旋修改元素指针指向时,节点3双亲的指针对应也应修改为指向节点5。

//左单旋
	void RotateLeft(Node* parent)
	{
		Node*subR = parent->_right;
		Node* subRL = subR->_left;

		//进行①指针的指向修改
		parent->_right = subRL;
		//subRL可能为空
		if (subRL)
		{
			subRL->_parent = parent;
		}

		//进行②指针的指向修改
		subR->_left = parent;
		Node* pParent = parent->_parent;
		parent->_parent = subR;
		subR->_parent = pParent;
		
		//parent为根节点时,subR直接为新的根节点
		//parent不为根节点时,parent可能为pParent的左孩子或是右孩子
		if (pParent == nullptr)
		{
			_root = subR;
		}
		else
		{
			if (parent == pParent->_left)
			{
				pParent ->_left = subR;
			}
			else
			{
				pParent->_right = subR;
			}
		}
		//将平衡因子进行订正更新
		parent->_bf = subR->_bf = 0;
	}

3.左右双旋

场景:当新节点插入到较高左子树的右侧时。
在这里插入图片描述在这里插入图片描述
综上,我们可以看到,在向上述AVL树中插入元素5.5和元素6.5的处理方法一致。

//左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateLeft(parent->_left);
		RotateRight(parent);

		//双旋后进行平衡因子的更新
		if (bf == 1)
		{
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
		}
		else
			;//bf为0的时候不需要更新
	}

4.右左双旋

场景:当新节点插入到较高右子树的左侧时。
在这里插入图片描述在这里插入图片描述
综上,我们可以看到,在向上述AVL树中插入元素3.5和元素4.5的处理方法一致。

//右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateRight(parent->_right);
		RotateLeft(parent);

		if (bf == 1)
			parent->_bf = -1;
		else if (bf==-1)
			subR->_bf = 1;
		else
			;
	}

四、AVL树的检测

1.检测思路

因为AVL树是在二叉搜索树的基础上加入了平衡性的限制条件,因此要验证AVL树,我们可以先验证其是否为二叉搜索树,再验证其节点的平衡因子。

step1:验证其为二叉搜索树
如果中序遍历可得到一个有序的序列,就说明其为二叉搜索树。

step2:验证其为平衡树

a.每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)。

b.节点的平衡因子是否计算正确。

2.实现

bool _IsValidAVLTree(Node* root)
	{
		if (root == nullptr)
			return true;
		//树非空,用平衡因子计算公式进行树的平衡因子的检测
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		if (abs(rightHeight - leftHeight) > 1 || ((rightHeight - leftHeight) != root->_bf))
		{
			cout << root->_data << " " << root->_bf << " " << rightHeight - leftHeight << endl;
			//节点处的平衡因子出错
			//前者为创建的树更新的平衡因子
			//后者根据平衡因子计算公式进行数据计算
			return false;
		}
		return _IsValidAVLTree(root->_left) && _IsValidAVLTree(root->_right);
	}

五、AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即log_2 (N)。

但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

六、测试结果展示及完整代码

//测试函数//
void TestAVLTree()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int> t;

	for (auto e : a)
	{
		t.Insert(e);
	}

	t.Inorder();

	if (t.IsValidAVLTree())
	{
		cout << endl;
		cout << "t is valid AVLTree" << endl;
	}
	else
	{
		cout << endl;
		cout << "t is invalid AVLTree" << endl;
	}
}

在这里插入图片描述

AVL.hpp
#pragma once

template<class T>
class AVLTreeNode
{
public:
	AVLTreeNode<T>* _left;//节点左孩子
	AVLTreeNode<T>* _right;//节点右孩子
	AVLTreeNode<T>* _parent;//节点双亲
	T _data; //节点值域
	int _bf; //该节点的平衡因子

	AVLTreeNode(const T& data=T())
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _bf(0)
	{}
};

//规定:树中元素唯一
template<class T>
class AVLTree
{
	typedef AVLTreeNode<T> Node;
public:
	AVLTree()
		:_root(nullptr)
	{}

	~AVLTree()
	{
		_Destroy(_root);
	}

	//AVL树节点插入
	//1.按照二叉搜索树的方式插入新节点
	//2.调整节点的平衡因子(平衡因子规定为:右子树高度-左子树高度)
	bool Insert(const T& data)
	{
		if (_root == nullptr)
		{
			//新插入节点即为根节点
			_root = new Node(data);
			return true;
		}
		//1.按照二叉搜索树插入新节点方式往AVL树中插入新节点
		//a.查找带插入节点在树中是否存在
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (data < cur->_data)
				cur = cur->_left;
			else if (data>cur->_data)
				cur =cur->_right;
			else
				return false;
		}
		//b.走下来表示未找到,进行节点的插入
		cur = new Node(data);
		if (data < parent->_data)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		cur->_parent = parent;

		//节点插入成功后,其parent的子树高度改变,故需更新parent的平衡因子
		while (parent)
		{
			if (cur == parent->_left)
			{
				parent->_bf--;
			}
			else
			{
				parent->_bf++;
			}

			/
			if (parent->_bf == 0)//说明节点在插入之前parent有一孩子,插入新节点并未影响整体的树的高度,只是影响parent的平衡因子
				break;
			else if (1==parent->_bf  || -1==parent->_bf )
			{
				//说明以parent为根的二叉树高度增加了一层,需要继续往上更新平衡因子
				cur = parent;
				parent = cur->_parent;
			}
			else //parent的平衡因子为2或-2
			{
				if (parent->_bf == 2)
				{
					//说明其右子树高
					if (cur->_bf == 1)
					{
						RotateLeft(parent);//右子树高且为较高右子树的右侧,故左单旋
					}
					else
					{
						//右子树较高且为较高右子树的左侧,故子树先右单旋再整体左单旋,即右左双旋
						RotateRL(parent);
						//等价于:先RotateRight(parent->_right)再RotateLeft(parent)
					}
				}
				else
				{
					//说明其左子树高
					if (cur->_bf == -1)
					{
						RotateRight(parent);//左子树较高且为较高左子树的左侧,故右单旋
					}
					else
					{
						//左子树较高且为较高左子树的右侧,故子树先左单旋再整体右单旋,即左右双旋
						RotateLR(parent);
						//等价于:先RotateLeft(parent->_left)再RotateRight(parent)
					}
				}
				//在元素插入之前以parent为根的二叉树高度
				//插入元素后进行树的旋转之后以parent为根的二叉树的高度一样
				//故无需继续向上更新
				break;
			}
		}
		return true;
	}

	void Inorder()
	{
		cout << "树的中序遍历为:" << endl;
			_Inorder(_root);
	}
	
	//检测构造出来的树是否是AVL树
	bool IsValidAVLTree()
	{
		return _IsValidAVLTree(_root);
	}

private:
	void _DestroyAVLTree(Node*& root)
	{
		if (root)
		{
			_DestroyAVLTree(root->_left);
			_DestroyAVLTree(root->_right);
			delete root;
			root = nullptr;
		}
	}
	void _Inorder(Node* root)
	{
		if (root)
		{
			_Inorder(root->_left);
			cout << root->_data << " ";
			_Inorder(root->_right);
		}
	}
	//求树的高度
	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 _IsValidAVLTree(Node* root)
	{
		if (root == nullptr)
			return true;
		//树非空,用平衡因子计算公式进行树的平衡因子的检测
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		if (abs(rightHeight - leftHeight) > 1 || ((rightHeight - leftHeight) != root->_bf))
		{
			cout << root->_data << " " << root->_bf << " " << rightHeight - leftHeight << endl;
			//节点处的平衡因子出错,前者为创建的树更新的平衡因子,后者根据平衡因子计算公式进行数据计算
			return false;
		}
		return _IsValidAVLTree(root->_left) && _IsValidAVLTree(root->_right);
	}

	//左单旋
	void RotateLeft(Node* parent)
	{
		Node*subR = parent->_right;
		Node* subRL = subR->_left;

		//进行①指针的指向修改
		parent->_right = subRL;
		//subRL可能为空
		if (subRL)
		{
			subRL->_parent = parent;
		}

		//进行②指针的指向修改
		subR->_left = parent;
		Node* pParent = parent->_parent;
		parent->_parent = subR;
		subR->_parent = pParent;
		
		//parent为根节点时,subR直接为新的根节点
		//parent不为根节点时,parent可能为pParent的左孩子或是右孩子
		if (pParent == nullptr)
		{
			_root = subR;
		}
		else
		{
			if (parent == pParent->_left)
			{
				pParent ->_left = subR;
			}
			else
			{
				pParent->_right = subR;
			}
		}
		//将平衡因子进行订正更新
		parent->_bf = subR->_bf = 0;
	}

	//右单旋
	void RotateRight(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		//进行①指针的指向修改
		parent->_left = subLR;
		//subLR可能为空
		if (subLR)
		{
			subLR->_parent = parent;
		}

		//进行②指针的指向修改
		subL->_right = parent;
		Node* pParent = parent->_parent;
		parent->_parent = subL;
		subL->_parent = pParent;


		//parent为根节点时,subR直接为新的根节点
		//parent不为根节点时,parent可能为pParent的左孩子或是右孩子
		if (pParent == nullptr)
		{
			_root = subL;
		}
		else
		{
			if (parent == pParent->_left)
			{
				pParent->_left = subL;
			}
			else
			{
				pParent->_right = subL;
			}
		}
		//将平衡因子进行订正更新
		parent->_bf = subL -> _bf = 0;
	}

	//左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateLeft(parent->_left);
		RotateRight(parent);

		//双旋后进行平衡因子的更新
		if (bf == 1)
		{
			subL->_bf = -1;
		}
		else if (bf == -1)
		{
			parent->_bf = 1;
		}
		else
			;//bf为0的时候不需要更新
	}

	//右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateRight(parent->_right);
		RotateLeft(parent);

		if (bf == 1)
			parent->_bf = -1;
		else if (bf==-1)
			subR->_bf = 1;
		else
			;
	}
private:
	AVLTreeNode<T>* _root;
};

void TestAVLTree()
{
	int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	AVLTree<int> t;

	for (auto e : a)
	{
		t.Insert(e);
	}

	t.Inorder();

	if (t.IsValidAVLTree())
	{
		cout << endl;
		cout << "t is valid AVLTree" << endl;
	}
	else
	{
		cout << endl;
		cout << "t is invalid AVLTree" << endl;
	}
}
AVL.cpp
#include<iostream>
using namespace std;
#include"AVL.hpp"

int main()
{
	TestAVLTree();
	system("pause");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值