【C++】AVL树

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

文章目录

  • 1、AVL树的概念
  • 2、AVL树的实现
    • 2-1、AVL树节点的定义
    • 2-2、AVL树的插入
    • 2-3、AVL树的旋转
    • 2-4、AVL树的验证
    • 2-5、中序遍历
    • 2-6、AVL树的删除
    • 2-7、AVL树的性能
  • 3、AVL树模拟实现代码


1、AVL树的概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

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

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

简单来说,AVL树的任何一个节点作为根节点来看的话,它左右子树的最高层数和最低层数差值只能小于等于1

【C++】AVL树_第1张图片

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

2、AVL树的实现

首先,我们对于AVL树的实现只学习树的插入,不学习删除等其他操作,因为AVL树的插入就包含了重要内容(旋转和平衡因子),我们学会插入就足够了,以后面试一般只会让你说一下AVL树的旋转是怎么旋转的,代码基本上不会让你写,因为一是费时间,二是你写的hr一时半会也看不出细节有什么问题。再退一步来讲,hr就算要你写代码也顶天让你写个插入,这一点就足以看出我们对于平衡搜索二叉树的理解了

我们这里就用前面的KV键值对来实现(其实大概率只是使用K值)

2-1、AVL树节点的定义

template <class K, class V>
class AVLTreeNode
{
public:
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;//左节点
	AVLTreeNode<K, V>* _right;//右节点
	AVLTreeNode<K, V>* _parent;//父节点

	int _bf;//平衡因子
	//这里采用右插入平衡因子+1;左插入平衡因子-1

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

2-2、AVL树的插入

AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

1. 按照二叉搜索树的方式插入新节点
2. 调整节点的平衡因子
bool Insert(const pair<K, V>& kv)
{
	if (_root == nullptr)
	{
		_root = new Node(kv);
		return true;
	}
	Node* parent = nullptr;//parent是cur的父节点
	Node* cur = _root;//cur往下走
	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;//不存在找到,因为AVL树不允许有重复值
		}
	}
	走到这里就表示找到我们要插入kv值的正确位置了
	cur = new Node(kv);
	if (parent->_kv.first < kv.first)//如果new的节点比父节点大,那么父节点的右指针指向new节点
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else//如果new的节点比父节点小,那么父节点的左指针指向new节点
	{
		parent->_left = cur;
		cur->_parent = parent;
	}
	开始更新平衡因子
	while (parent)//更新到根节点才算更新完平衡因子
	{
		//1、如果是右子树新增,那么父节点的_bf就加一
		//2、如果是左子树新增,那么父节点的_bf就减一
		//+1和-1大家可以自己决定,只要是对的,怎么都行!
		if (cur == parent->_right)
		{
			parent->_bf++;
		}
		else
		{
			parent->_bf--;
		}
		// 是否继续更新依据:子树的高度是否变化
		// 1、parent->_bf == 0说明之前parent->_bf是 1 或者 -1
		// 说明之前parent一边高一边低,这次插入填上矮的那边,parent所在子树高度不变,不需要继续往上更新
		// 2、parent->_bf == 1 或 -1 说明之前是parent->_bf == 0,两边一样高,现在插入一边更高了,
		// parent所在子树高度变了,继续往上更新
		// 3、parent->_bf == 2 或 -2,说明之前parent->_bf == 1 或者 -1,现在插入严重不平衡,违反规则
		// 就地处理--旋转

		// 旋转:
		// 1、让这颗子树左右高度不超过1
		// 2、旋转过程中继续保持他是搜索树
		// 3、更新调整孩子节点的平衡因子
		// 4、让这颗子树的高度跟插入前保持一致

		//如果新增节点cur,使得父节点parent的平衡因子变成了0,那么表示该插入节点对整棵树的平衡因子没有影响
		//不用向上判断,可以直接退出
		if (parent->_bf == 0)
		{
			break;
		}
		//如果新增cur使得父节点parent的平衡因子变成了1或者-1,那么我们要继续向上判断是否对上面的节点的
		//平衡因子产生了影响
		else if (parent->_bf == 1 || parent->_bf == -1)
		{
			cur = parent;
			parent = parent->_parent;
		}
		这里就表示树结构出问题了,不再是一颗AVL树了,我们就要开始旋转!!!
		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)//左右旋
			{
				RotateLR(parent);
			}
			else if (parent->_bf == 2 && cur->_bf == -1)//右左旋
			{
				RotateRL(parent);
			}
			else
			{
				assert(false);
			}
			break;//旋转完一次就可以退出了,因为旋转的时候我们已经向上判断了,除非新插入,否则树就是avl树
		}
		else
		{
			assert(false);//这里直接报错,走到这里树就已经不是AVL树了
		}
	}
	return true;
}

2-3、AVL树的旋转

如果在一棵原本是平衡的AVL树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,使之平衡化。根据节点插入位置的不同,AVL树的旋转分为四种:
1. 新节点插入较高左子树的左侧—左左:右单旋
【C++】AVL树_第2张图片
2. 新节点插入较高右子树的右侧—右右:左单旋
【C++】AVL树_第3张图片
3. 新节点插入较高左子树的右侧—左右:先左单旋再右单旋
【C++】AVL树_第4张图片

将双旋变成单旋后再旋转,即:先对30进行左单旋,然后再对90进行右单旋,旋转完成后再考虑平衡因子的更新。

4. 新节点插入较高右子树的左侧—右左:先右单旋再左单旋
【C++】AVL树_第5张图片

接下来是代码实现:

void RotateL(Node* parent)//左旋
{
	Node* subr = parent->_right;
	Node* subrl = subr->_left;
	parent->_right = subrl;
	if (subrl)//上面的节点不可能为空,但是这里的subrl可能为空
	{
		subrl->_parent = parent;
	}
	这里要一直向上判断树是不是avl树,因为这个parent可能是根节点,可能不是
	如果不是根节点,那么就要继续向上面判断,是否需要旋转,知道整棵树都判断完毕
	Node* ppnode = parent->_parent;//记录父节点的父节点,如果为空,表示树不需要旋转了,否则继续向上找
	subr->_left = parent;
	parent->_parent = subr;
	if (ppnode == nullptr)//如果
	{
		_root = subr;
		_root->_parent = nullptr;//这里要置空
	}
	else
	{
		if (ppnode->_left == parent)
		{
			ppnode->_left = subr;
		}
		else
		{
			ppnode->_right = subr;
		}
		subr->_parent = ppnode;
	}
	parent->_bf = subr->_bf = 0;//更新平衡因子,左旋之后,parent和subr的左右子树节点数是一样的!
}

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 == nullptr)
	{
		_root = subl;
		subl->_parent = nullptr;
	}
	else
	{
		if (ppnode->_left == parent)
		{
			ppnode->_left = subl;
		}
		else
		{
			ppnode->_right = subl;
		}
		subl->_parent = ppnode;
	}
	parent->_bf = subl->_bf = 0;
}

void RotateLR(Node* parent)//左右双旋
{
	Node* subl = parent->_left;
	Node* sublr = subl->_right;

	int bf = sublr->_bf;
	RotateL(parent->_left);
	RotateR(parent);

	if (bf == -1)//sublr左子树新增
	{
		subl->_bf = 0;
		parent->_bf = 1;
		sublr->_bf = 0;
	}
	else if (bf == 1)//sublr右子树新增
	{
		subl->_bf = -1;
		parent->_bf = 0;
		sublr->_bf = 0;
	}
	else if (bf == 0)//sublr自己是新增
	{
		subl->_bf = 0;
		parent->_bf = 0;
		sublr->_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);

	if (bf == -1)//sublr左子树新增
	{
		subr->_bf = 1;
		parent->_bf = 0;
		subrl->_bf = 0;
	}
	else if (bf == 1)//sublr右子树新增
	{
		subr->_bf = 0;
		parent->_bf = -1;
		subrl->_bf = 0;
	}
	else if (bf == 0)//sublr自己是新增
	{
		subr->_bf = 0;
		parent->_bf = 0;
		subrl->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

总结:
假如以pParent为根的子树不平衡,即pParent的平衡因子为2或者-2,分以下情况考虑

  1. pParent的平衡因子为2,说明pParent的右子树高,设pParent的右子树的根为pSubR

当pSubR的平衡因子为1时,执行左单旋
当pSubR的平衡因子为-1时,执行右左双旋

  1. pParent的平衡因子为-2,说明pParent的左子树高,设pParent的左子树的根为pSubL

当pSubL的平衡因子为-1是,执行右单旋
当pSubL的平衡因子为1时,执行左右双旋

旋转完成后,原pParent为根的子树个高度降低,已经平衡,不需要再向上更新。

2-4、AVL树的验证

AVL树是在二叉搜索树的基础上加入了平衡性的限制,因此要验证AVL树,可以分两步:

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

  2. 验证其为平衡树
    每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
    节点的平衡因子是否计算正确

int Height(Node* root)//树高
{
	if (root == nullptr)
	{
		return 0;
	}
	int lefttree = Height(root->_left);
	int righttree = Height(root->_right);
	return lefttree > righttree ? lefttree + 1 : righttree + 1;
}

bool IsBalance()//判断是否为AVL树
{
	return _Balance(_root);
}
bool _Balance(Node* root)判断是否为AVL树
{
	if (root == nullptr)
	{
		return true;
	}
	int leftheight = Height(root->_left);
	int rightheight = Height(root->_right);
	if (rightheight - leftheight != root->_bf)
	{
		cout << root->_kv.first << "平衡因子异常!!!" << endl;
		return false;
	}
	return abs(rightheight - leftheight) < 2 && _Balance(root->_left) && _Balance(root->_right);
}

2-5、中序遍历

void Inorder()
//遍历要用到递归,这里我们不能使用_root,因为_root是private成员,所以类外面直接使用_Inorder函数不行
{
	_Inorder(_root);
}
void _Inorder(Node* root)//中序遍历
{
	if (root == nullptr)
	{
		return;
	}
	_Inorder(root->_left);
	cout << root->_kv.first << ":" << root->_kv.second << endl;
	_Inorder(root->_right);
}

2-6、AVL树的删除

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子,只不过与删除不同的时,删除节点后的平衡因子更新,最差情况下一直要调整到根节点的位置。具体实现可参考《算法导论》或《数据结构-用面向对象方法与C++描述》殷人昆版。

2-7、AVL树的性能

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

3、AVL树模拟实现代码

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
using namespace std;
template <class K, class V>
class AVLTreeNode
{
public:
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;//左节点
	AVLTreeNode<K, V>* _right;//右节点
	AVLTreeNode<K, V>* _parent;//父节点

	int _bf;//平衡因子

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

template <class K, class V>
class 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;//parent是cur的父节点
		Node* cur = _root;//cur往下走
		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;//不存在找到,因为AVL树不允许有重复值
			}
		}
		走到这里就表示找到我们要插入kv值的正确位置了
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)//如果new的节点比父节点大,那么父节点的右指针指向new节点
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else//如果new的节点比父节点小,那么父节点的左指针指向new节点
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		开始更新平衡因子
		while (parent)//更新到根节点才算更新完平衡因子
		{
			//1、如果是右子树新增,那么父节点的_bf就加一
			//2、如果是左子树新增,那么父节点的_bf就减一
			//+1和-1大家可以自己决定,只要是对的,怎么都行!
			if (cur == parent->_right)
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}
			// 是否继续更新依据:子树的高度是否变化
			// 1、parent->_bf == 0说明之前parent->_bf是 1 或者 -1
			// 说明之前parent一边高一边低,这次插入填上矮的那边,parent所在子树高度不变,不需要继续往上更新
			// 2、parent->_bf == 1 或 -1 说明之前是parent->_bf == 0,两边一样高,现在插入一边更高了,
			// parent所在子树高度变了,继续往上更新
			// 3、parent->_bf == 2 或 -2,说明之前parent->_bf == 1 或者 -1,现在插入严重不平衡,违反规则
			// 就地处理--旋转

			// 旋转:
			// 1、让这颗子树左右高度不超过1
			// 2、旋转过程中继续保持他是搜索树
			// 3、更新调整孩子节点的平衡因子
			// 4、让这颗子树的高度跟插入前保持一致

			//如果新增节点cur,使得父节点parent的平衡因子变成了0,那么表示该插入节点对整棵树的平衡因子没有影响
			//不用向上判断,可以直接退出
			if (parent->_bf == 0)
			{
				break;
			}
			//如果新增cur使得父节点parent的平衡因子变成了1或者-1,那么我们要继续向上判断是否对上面的节点的
			//平衡因子产生了影响
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			这里就表示树结构出问题了,不再是一颗AVL树了,我们就要开始旋转!!!
			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)//左右旋
				{
					RotateLR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)//右左旋
				{
					RotateRL(parent);
				}
				else
				{
					assert(false);
				}
				break;//旋转完一次就可以退出了,因为旋转的时候我们已经向上判断了,除非新插入,否则树就是avl树
			}
			else
			{
				assert(false);//这里直接报错,走到这里树就已经不是AVL树了
			}
			
		}
		return true;
	}
	void RotateL(Node* parent)
	{
		Node* subr = parent->_right;
		Node* subrl = subr->_left;
		parent->_right = subrl;
		if (subrl)//上面的节点不可能为空,但是这里的subrl可能为空
		{
			subrl->_parent = parent;
		}
		这里要一直向上判断树是不是avl树,因为这个parent可能是根节点,可能不是
		如果不是根节点,那么就要继续向上面判断,是否需要旋转,知道整棵树都判断完毕
		Node* ppnode = parent->_parent;//记录父节点的父节点,如果为空,表示树不需要旋转了,否则继续向上找
		subr->_left = parent;
		parent->_parent = subr;
		if (ppnode == nullptr)//如果
		{
			_root = subr;
			_root->_parent = nullptr;//这里要置空
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subr;
			}
			else
			{
				ppnode->_right = subr;
			}
			subr->_parent = ppnode;
		}
		parent->_bf = subr->_bf = 0;//更新平衡因子,左旋之后,parent和subr的左右子树节点数是一样的!
	}
	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 == nullptr)
		{
			_root = subl;
			subl->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subl;
			}
			else
			{
				ppnode->_right = subl;
			}
			subl->_parent = ppnode;
		}
		parent->_bf = subl->_bf = 0;
	}
	void RotateLR(Node* parent)
	{
		Node* subl = parent->_left;
		Node* sublr = subl->_right;

		int bf = sublr->_bf;
		RotateL(parent->_left);
		RotateR(parent);

		if (bf == -1)//sublr左子树新增
		{
			subl->_bf = 0;
			parent->_bf = 1;
			sublr->_bf = 0;
		}
		else if (bf == 1)//sublr右子树新增
		{
			subl->_bf = -1;
			parent->_bf = 0;
			sublr->_bf = 0;
		}
		else if (bf == 0)//sublr自己是新增
		{
			subl->_bf = 0;
			parent->_bf = 0;
			sublr->_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);

		if (bf == -1)//sublr左子树新增
		{
			subr->_bf = 1;
			parent->_bf = 0;
			subrl->_bf = 0;
		}
		else if (bf == 1)//sublr右子树新增
		{
			subr->_bf = 0;
			parent->_bf = -1;
			subrl->_bf = 0;
		}
		else if (bf == 0)//sublr自己是新增
		{
			subr->_bf = 0;
			parent->_bf = 0;
			subrl->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	void Inorder()//中序遍历
	{
		_Inorder(_root);
	}
	void _Inorder(Node* root)//中序遍历
	{
		if (root == nullptr)
		{
			return;
		}
		_Inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_Inorder(root->_right);
	}
	int Height(Node* root)//树高
	{
		if (root == nullptr)
		{
			return 0;
		}
		int lefttree = Height(root->_left);
		int righttree = Height(root->_right);
		return lefttree > righttree ? lefttree + 1 : righttree + 1;
	}
	
	bool IsBalance()//判断是否为AVL树
	{
		return _Balance(_root);
	}
	bool _Balance(Node* root)判断是否为AVL树
	{
		if (root == nullptr)
		{
			return true;
		}
		int leftheight = Height(root->_left);
		int rightheight = Height(root->_right);
		if (rightheight - leftheight != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常!!!" << endl;
			return false;
		}
		return abs(rightheight - leftheight) < 2 && _Balance(root->_left) && _Balance(root->_right);
	}
private:
	Node* _root = nullptr;
};

void test()//测试代码
{
	//int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
	//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
	int a[] = { 1,2,3 };
	//AVLTree q;
	//for (auto e : a)
	//{
	//	q.Insert(make_pair(e, e));
	//}

	//q.Inorder();
	//cout << q.IsBalance();

	srand(time(0));
	const size_t N = 10;
	AVLTree<int, int> t;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand();
		t.Insert(make_pair(x, x));
		//cout << t.IsBalance() << endl;
	}

	//t.Inorder();

	cout << t.IsBalance() << endl;
}


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