二叉树之AVL树

一、AVL树的概念

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

AVL树的性质

a:它的左右子树都是AVL树

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

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

二、AVL树的实现

1、结点的定义

template<class T>
struct AVLNode {
	AVLNode(T key = T())
		:left(nullptr)
		, right(nullptr)
		,parent(nullptr)
		, _key(key)
		, bf(0);
	{}
	T _key;
	int bf;//平衡因子
	AVLNode<T>* left;
	AVLNode<T>* right;
	AVLNode<T>* parent;//该结点的父结点(双亲)
};

这里要用三叉链的原因是方便向上找祖先的平衡因子。 (也可以用栈来存储平衡因子,这样就不用三叉链了)。

2、AVL树的插入

分析:

插入结点会影响部分祖先结点的平衡因子,所以要更新平衡因子。

这里我定义的高度差是右树减左树,所以插入结点父结点的平衡因子与插入结点的位置有关。

插在左子树平衡因子 -1;插在右子树平衡因子+1;

至于是否继续向上更新祖先的平衡因子,是要看以插入结点的父结点作为根的子树的高度是否会变化。

1、parent的平衡因子==0

说明parent的平衡因子更新前是 1 or -1,插入结点插入在矮的一侧,此时parent所在子树高度不变,不需要向上更新平衡因子。

2、parent的平衡因子==1 or -1

说明parent的平衡因子更新前是0,插入结点插入在parent的左右任意一侧,此时parent所在子树的高度发生变化,需要向上更新平衡因子。

3、parent的平衡因子== 2 or -2

说明parent的平衡因子更新前是1 or -1,插入结点插入在高的一侧,进一步加剧了左右高度差,此时需要旋转来操作平衡。

大体框架:

1、除去旋转的大体框架
bool insert(const T& key)
{
	if (_root == nullptr)//空树的情况
	{
		_root = new Node(key);
		return true;
	}
	Node* pcur = _root;
	Node* pre = nullptr;
	while (pcur)//插入树里面不存在的值,pcur一定会走向空
	{
		pre = pcur;//插入位置的父节点
		if (pcur->_key < key)
		{
			pcur = pcur->right;
		}
		else if (pcur->_key > key)
		{
			pcur = pcur->left;
		}
		else//插入的值已存在,无需插入
		{
			return false;
		}
	}
	pcur = new Node(key);//作为插入的新结点
	if (pre->_key < key)
		pre->right = pcur;
	else 
		pre->left = pcur;
	pcur->parent = pre;//链接父子结点
	// 插入完成后更新并检查平衡因子,不满足平衡条件的,旋转
	while (pre)
	{
		//更新平衡因子
		if(pre->right == pcur)
			++pre->bf;//插在右子树平衡因子+1
		if(pre->left == pcur)
			--pre->bf;//插在左子树平衡因子 -1
		//更新后检查平衡因子
		if (pre->bf == 0)
			break;
		else if (pre->bf == -1 || pre->bf == 1)//插入前平衡因子是0,插入后改变了树的高度
		{
			pcur = pre;//pcur=pcur->parent
			pre = pre->parent;//向上继续判断
		}
		else if (pre->bf == 2 || pre->bf == -2)//插入前平衡因子不是1就是-1
		{
			//旋转处理
			//注意旋转处理完了要跳出循环
			break;//此时要跳出循环,(因为旋转后,原来根结点的位置发生改变,且此时平衡因子出现问题的那棵子树达到平衡),不然会出问题(因为这个调试了半天)
		}
		else
			assert(false);//平衡因子绝对值大于2说明旋转操作有问题,此时断言错误
	}
	return true;
}
2、旋转的分析

情况一:单纯的左子树高(插在subL的左子树上)

此时需要右单旋来达到平衡:

if (pre->bf == -2 && pcur->bf == -1)//左子树高
				RotateR(pre);
//右单旋(左子树高)此时插入结点的位置是subL的左子树上
void RotateR(Node* root)
{
	Node* subL = root->left;//左子树的根结点
	Node* subLR = subL->right;
	root->left = subLR;
	if (subLR)//不为空则链接
	{
		subLR->parent = root;
	}
	subL->right = root;
	if (root->parent == nullptr)//整个二叉树的根结点出现问题
	{
		_root = subL;//更新根结点
	}
	else//更换了子树根的位置,需要重新链接,避免出现4插链的情况
	{
		if (root == root->parent->left)
			root->parent->left = subL;
		if(root == root->parent->right)
			root->parent->right = subL;
	}
	subL->parent = root->parent;
	root->parent = subL;
	//更新平衡因子
	subL->bf = 0;
	root->bf = 0;
}

情况二:单纯的右子树高(插在subR的右子树上)

此时需要左单旋来达到平衡:

 

 

else if(pre->bf == 2 && pcur->bf == 1)//右子树高
				RotateL(pre);
//左单旋(右子树高)此时插入结点的位置是subR的右子树上
void RotateL(Node* root)
{
	Node* subR = root->right;
	Node* subRL = subR->left;
	root->right = subRL;
	if (subRL)//不为空
	{
		subRL->parent = root;
	}
	subR->left = root;
	if (root->parent == nullptr)//整个二叉树的根结点出现问题
	{
		_root = subR;//更新根结点
	}
	else//更换了子树根的位置,需要重新链接,避免出现4插链的情况
	{
		if (root == root->parent->left)
			root->parent->left = subR;
		if (root == root->parent->right)
			root->parent->right = subR;
	}
	subR->parent = root->parent;
	root->parent = subR;
	//更新平衡因子
	root->bf = 0;
	subR->bf = 0;
}

情况三:左子树高,但是插在subL的右子树上

此时需要先以subL(pcur)为根左旋,然后再以root(pre)为根右旋来达到平衡:

1、以subL(pcur)为根左旋:

2、以root(pre)为根右旋:

整个具体的过程:

这里的平衡因子还有两种情况要分开讨论:

(1)add插在subLR的左子树上(subLR->bf==-1):此时两次旋转完成后subL的平衡因子为0,root的平衡因子为1

(2)add插在subLR的右子树上(subLR->bf==1):此时两次旋转完成后subL的平衡因子为-1,root的平衡因子为0

else if (pre->bf == -2 && pcur->bf == 1)//左子树高但是插在subL的右子树上
{
				RotateLR(pre);
}
//左旋+右旋(左子树高但是插在subL的右子树上)
void RotateLR(Node* root)
{
	Node* subL = root->left;
	Node* subLR = subL->right;
	int bf = subLR->bf;//记录,用于判断更新情况
	RotateL(subL);
	RotateR(root);
	if (bf == 0)
	{
		root->bf = 0;
		subL->bf = 0;
		subLR->bf = 0;
	}
	else if (bf == -1)
	{
		root->bf = 1;
		subL->bf = 0;
		subLR->bf = 0;
	}
	else if (bf == 1)
	{
		root->bf = 0;
		subL->bf = 0;
		subLR->bf = 1;
	}
	else
		assert(false);
}

情况四:右子树高但插在subR的左子树上

此时需要先以subR(pcur)为根右旋,然后再以root(pre)为根左旋来达到平衡:

1、以subR(pcur)为根右旋:

2、以root(pre)为根左旋:

整个具体的过程:

这里的平衡因子还有两种情况要分开讨论:

(1)add插在subRL的左子树上(subRL->bf==-1):此时两次旋转完成后subR的平衡因子为1,root的平衡因子为0

(2)add插在subLR的右子树上(subRL>bf==1):此时两次旋转完成后subR的平衡因子为0,root的平衡因子为-1

else if (pre->bf == 2 && pcur->bf == -1)//右子树高但是插在subR的左子树上
{
				RotateRL(pre);
}
//右旋+左旋(右子树高但是插在subR的左子树上)
void RotateRL(Node* root)
{
	Node* subR = root->right;
	Node* subRL = subR->left;
	int bf = subRL->bf;
	RotateR(subR);
	RotateL(root);
	if (bf == 0)
	{
		subR->bf = 0;
		subRL->bf = 0;
		root->bf = 0;
	}
	else if (bf == 1)
	{
		subR->bf = 0;
		subRL->bf = 0;
		root->bf = -1;
	}
	else if (bf == -1)
	{
		subR->bf = 1;
		subRL->bf = 0;
		root->bf = 0;
	}
	else
		assert(false);
}

三、 AVL树的验证

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

1. 验证其为二叉搜索树:

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

2. 验证其为平衡树:

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

int _Height(Node* root)
{
	if (root == nullptr)
		return 0;
	int leftH = _Height(root->left);
	int rightH = _Height(root->right);
	return leftH > rightH ? leftH + 1 : rightH + 1;
}
bool IsBalance(Node* root)
{
	if (nullptr == root)
		return true;
	int leftHeight = _Height(root->left);
	int rightHeight = _Height(root->right);
	int diff = rightHeight - leftHeight;
	if (abs(diff) > 1)
	{
		cout << "高度差出现问题" << endl;
		return false;
	}
	if (diff != root->bf)
	{
		cout << "平衡因子出现问题" << endl;
		return false;
	}
	return IsBalance(root->left) && IsBalance(root->right);
}

测试用例:

int arr1[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
int arr2[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };

源码:

#include<iostream>
#include<assert.h>
using namespace std;
template<class T>
struct AVLNode {
	AVLNode(T key = T())
		:left(nullptr)
		, right(nullptr)
		, parent(nullptr)
		, _key(key)
		, bf(0)
	{}
	T _key;
	int bf;//平衡因子
	AVLNode<T>* left;
	AVLNode<T>* right;
	AVLNode<T>* parent;
};

// AVL: 二叉搜索树 + 平衡因子的限制
template<class T>
class AVLTree {
public:
	typedef AVLNode<T> Node;
	AVLTree() = default;
	~AVLTree()
	{
		Destroy(_root);
		_root = nullptr;
	}
	void InOrder()
	{
		_InOrder(_root);//成员函数可以访问私有
	}
	int Height()
	{
		return _Height(_root);
	}
	Node* GetNode()//给外界提供获得私有结点的方式,便于调试
	{
		return _root;
	}
	bool IsBalance(Node* root)
	{
		if (nullptr == root)
			return true;
		int leftHeight = _Height(root->left);
		int rightHeight = _Height(root->right);
		int diff = rightHeight - leftHeight;
		if (abs(diff) > 1)
		{
			cout << "高度差出现问题" << endl;
			return false;
		}
		if (diff != root->bf)
		{
			cout << "平衡因子出现问题" << endl;
			return false;
		}
		return IsBalance(root->left) && IsBalance(root->right);
	}
	bool find(T key)
	{
		Node* pcur = _root;
		while (pcur)
		{
			if (pcur->_key < key)
			{
				pcur = pcur->right;
			}
			else if (pcur->_key > key)
			{
				pcur = pcur->left;
			}
			else
				return true;
		}
		return false;
	}
	bool insert(const T& key)
	{
		if (_root == nullptr)//空树的情况
		{
			_root = new Node(key);
			return true;
		}
		Node* pcur = _root;
		Node* pre = nullptr;
		while (pcur)//插入树里面不存在的值,pcur一定会走向空
		{
			pre = pcur;//插入位置的父节点
			if (pcur->_key < key)
			{
				pcur = pcur->right;
			}
			else if (pcur->_key > key)
			{
				pcur = pcur->left;
			}
			else//插入的值已存在,无需插入
			{
				return false;
			}
		}
		pcur = new Node(key);//作为插入的新结点
		if (pre->_key < key)
			pre->right = pcur;
		else 
			pre->left = pcur;
		pcur->parent = pre;//链接父子结点
		// 插入完成后更新并检查平衡因子,不满足平衡条件的,旋转
		while (pre)
		{
			//更新平衡因子
			if(pre->right == pcur)
				++pre->bf;//插在右子树平衡因子+1
			if(pre->left == pcur)
				--pre->bf;//插在左子树平衡因子 -1
			//更新后检查平衡因子
			if (pre->bf == 0)
				break;
			else if (pre->bf == -1 || pre->bf == 1)//插入前平衡因子是0,插入后改变了树的高度
			{
				pcur = pre;//pcur=pcur->parent
				pre = pre->parent;//向上继续判断
			}
			else if (pre->bf == 2 || pre->bf == -2)//插入前平衡因子不是1就是-1
			{
				//旋转处理
				if (pre->bf == -2 && pcur->bf == -1)//左子树高
					RotateR(pre);
				else if(pre->bf == 2 && pcur->bf == 1)//右子树高
					RotateL(pre);
				else if (pre->bf == -2 && pcur->bf == 1)//左子树高但是插在subL的右子树上
				{
					RotateLR(pre);
				}
				else if (pre->bf == 2 && pcur->bf == -1)//右子树高但是插在subR的左子树上
				{
					RotateRL(pre);
				}
				break;//此时要跳出循环,(因为旋转后,原来根结点的位置发生改变,且此时平衡因子出现问题的那棵子树达到平衡),不然会出问题(因为这个调试了半天)
			}
			else
				assert(false);//平衡因子绝对值大于2说明旋转操作有问题,此时断言错误
		}
		return true;
	}
private:
	//右单旋(左子树高)此时插入结点的位置是subL的左子树上
	void RotateR(Node* root)
	{
		Node* subL = root->left;//左子树的根结点
		Node* subLR = subL->right;
		root->left = subLR;
		if (subLR)//不为空则链接
		{
			subLR->parent = root;
		}
		subL->right = root;
		if (root->parent == nullptr)//整个二叉树的根结点出现问题
		{
			_root = subL;//更新根结点
		}
		else//更换了子树根的位置,需要重新链接,避免出现4插链的情况
		{
			if (root == root->parent->left)
				root->parent->left = subL;
			if(root == root->parent->right)
				root->parent->right = subL;
		}
		subL->parent = root->parent;
		root->parent = subL;
		//更新平衡因子
		subL->bf = 0;
		root->bf = 0;
	}
	//左单旋(右子树高)此时插入结点的位置是subR的右子树上
	void RotateL(Node* root)
	{
		Node* subR = root->right;
		Node* subRL = subR->left;
		root->right = subRL;
		if (subRL)//不为空
		{
			subRL->parent = root;
		}
		subR->left = root;
		if (root->parent == nullptr)//整个二叉树的根结点出现问题
		{
			_root = subR;//更新根结点
		}
		else//更换了子树根的位置,需要重新链接,避免出现4插链的情况
		{
			if (root == root->parent->left)
				root->parent->left = subR;
			if (root == root->parent->right)
				root->parent->right = subR;
		}
		subR->parent = root->parent;
		root->parent = subR;
		//更新平衡因子
		root->bf = 0;
		subR->bf = 0;
	}
	//左旋+右旋(左子树高但是插在subL的右子树上)
	void RotateLR(Node* root)
	{
		Node* subL = root->left;
		Node* subLR = subL->right;
		int bf = subLR->bf;//记录,用于判断更新情况
		RotateL(subL);
		RotateR(root);
		if (bf == 0)
		{
			root->bf = 0;
			subL->bf = 0;
			subLR->bf = 0;
		}
		else if (bf == -1)
		{
			root->bf = 1;
			subL->bf = 0;
			subLR->bf = 0;
		}
		else if (bf == 1)
		{
			root->bf = 0;
			subL->bf = 0;
			subLR->bf = 1;
		}
		else
			assert(false);
	}
	//右旋+左旋(右子树高但是插在subR的左子树上)
	void RotateRL(Node* root)
	{
		Node* subR = root->right;
		Node* subRL = subR->left;
		int bf = subRL->bf;
		RotateR(subR);
		RotateL(root);
		if (bf == 0)
		{
			subR->bf = 0;
			subRL->bf = 0;
			root->bf = 0;
		}
		else if (bf == 1)
		{
			subR->bf = 0;
			subRL->bf = 0;
			root->bf = -1;
		}
		else if (bf == -1)
		{
			subR->bf = 1;
			subRL->bf = 0;
			root->bf = 0;
		}
		else
			assert(false);
	}
	void _InOrder(Node* root)//中序遍历
	{
		if (root == nullptr)
			return;
		_InOrder(root->left);
		cout << root->_key << " ";
		_InOrder(root->right);
	}
	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		int leftH = _Height(root->left);
		int rightH = _Height(root->right);
		return leftH > rightH ? leftH + 1 : rightH + 1;
	}
	void Destroy(Node* root)//逐一销毁结点
	{//后续删除
		if (root == nullptr)
			return;
		Destroy(root->left);
		Destroy(root->right);
		delete root;
	}
	Node* _root=nullptr;
};

四、AVL树的性能

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值