关联式容器构造【AVL树】分析与实现

AVL 树概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下

当向二叉搜索树中插入新结点后,保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),降低树的高度,从而减少平均搜索长度,这样的树即 AVL 树

一棵AVL树具有以下性质:

  1. 它的左右子树都是AVL树
  2. 左右子树高度之差(简称平衡因子)的绝对值不超过1
  3. AVL树有n个结点,其高度可保持在 O(log N) ,搜索时间复杂度 O(log N)

AVL 树实现

AVL 树定义

左孩子置空、右孩子置空、父亲节点置空、平衡因子置 0

AVL 树插入

  1. 插入(先按照二叉搜索树的规律插入)
  2. 更新平衡因子
  3. 判断平衡因子
    a:平衡因子为 0,停止更新,插入结束
    b:平衡因子为 1 / -1,继续向上更新
    c:平衡因子为 2 / -2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理

旋转操作:
新节点插入较高左子树的左侧—左左:右单旋在这里插入图片描述
新节点插入较高右子树的右侧—右右:左单旋
在这里插入图片描述
新节点插入较高左子树的右侧—左右:先左单旋再右单旋
在这里插入图片描述
新节点插入较高右子树的左侧—右左:先右单旋再左单旋
在这里插入图片描述

AVL树验证

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

  1. 验证其为二叉搜索树
    如果中序遍历可得到一个有序的序列,就说明为二叉搜索树
  2. 验证其为平衡树
    每个节点子树高度差的绝对值不超过1(注意节点中如果没有平衡因子)
    节点的平衡因子是否计算正确

AVL 树代码

#include <iostream>
#include <assert.h>
using namespace std;

template<class T>
struct AVLNode
{
	AVLNode<T>* _left;
	AVLNode<T>* _right;
	AVLNode<T>* _parent;
	T _value;
	//平衡因子
	int _bf;

	AVLNode(const T& value = T())
		: _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _value(value)
		, _bf(0)
	{}
};

template<class T>
class AVLTree
{
public:
	typedef AVLNode<T> Node;
	typedef Node* pNode;

	void RotateR(pNode parent)//右旋
	{
		PNode pSubL = parent->_left;
		PNode pSubLR = pSubL->_right;
		parent->_left = pSubLR;
		
		if (pSubLR)
			pSubLR->_parent = parent;
		pSubL->_right = parent;
		PNode pParent = parent->_parent;
		parent->_parent = pSubL;
		pSubL->_parent = pParent;
		if (NULL == pParent)
		{
			_root = pSubL;
			pSubL->_parent = NULL;
		}
		else
		{
			if (pParent->_left == parent)
				pParent->_left = pSubL;
			else
				pParent->_right = pSubL;
		}
		// 根据调整后的结构更新部分节点的平衡因子
		parent->_bf = pSubL->_bf = 0;
	}

	void RotateL(pNode parent)//左旋
	{
		PNode pSubR = parent->_right;
		PNode pSubRL = pSubR->_left;
		parent->_right = pSubRL;

		if (pSubRL)
			pSubRL->_parent = parent;
		pSubR->_left = parent;
		PNode pParent = parent->_parent;
		parent->_parent = pSubR;
		pSubR->_parent = pParent;
		if (NULL == pParent)
		{
			_root = pSubR;
			pSubR->_parent = NULL;
		}
		else
		{
			if (pParent->_right == parent)
				pParent->_right = pSubR;
			else
				pParent->_left = pSubR;
		}
		// 根据调整后的结构更新部分节点的平衡因子
		parent->_bf = pSubL->_bf = 0;
	}

	void RotateLR(PNode pParent) //左右
	{
		PNode pSubL = pParent->_left;
		PNode pSubLR = pSubL->_right;

		// 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
			int bf = pSubLR->_bf;

		// 先进行左单旋
		RotateL(pParent->_pLeft);

		// 再进行右单旋
		RotateR(pParent);
		if (1 == bf)
			pSubL->_bf = -1;
		else if (-1 == bf)
			pParent->_bf = 1;
	}

	void RotateRL(PNode pParent)//右左
	{
		PNode pSubR = pParent->_right;
		PNode pSubRL = pSubR->_left;

		// 旋转之前,保存pSubLR的平衡因子,旋转完成之后,需要根据该平衡因子来调整其他节点的平衡因子
		int bf = pSubRL->_bf;

		// 先进行右单旋
		RotateR(pParent->_left);

		// 再进行左单旋
		RotateL(pParent);
		if (1 == bf)
			pSubR->_bf = -1;
		else if (-1 == bf)
			pParent->_bf = 1;
	}

	bool insert(const T& value)
	{
		// 1. 先按照二叉搜索树的规则将节点插入到AVL树中
		// 如果树为空,直接插入
		if (nullptr == _root)
		{
			_root = new pNode(data);
			return true;
		}
		// 按照二叉搜索树的性质查找data在树中的插入位置
		pNode pCur = _root;
		// 记录pCur的双亲,因为新元素最终插入在pCur双亲左右孩子的位置
		pNode pParent = nullptr;
		while (pCur)
		{
			pParent = pCur;
			if (data < pCur->_value)
				pCur = pCur->_left;
			else if (data > pCur->_value)
				pCur = pCur->_right; // 元素已经在树中存在
			else
				return false;
		}
		// 插入元素
		pCur = new pNode(data);
		if (data < pParent->_value)
			pParent->_left = pCur;
		else
			pParent->_right = pCur;
		// 2. 新节点插入后,AVL树的平衡性可能会遭到破坏,此时就需要更新平衡因子,
		// 的平衡性

		/*
		pCur插入后,pParent的平衡因子一定需要调整,在插入之前,pParent
		的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:
		1. 如果pCur插入到pParent的左侧,只需给pParent的平衡因子-1即可
		2. 如果pCur插入到pParent的右侧,只需给pParent的平衡因子+1即可

		此时:pParent的平衡因子可能有三种情况:0,正负1, 正负2
		1. 如果pParent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满
		足
		AVL树的性质,插入成功
		2. 如果pParent的平衡因子为正负1,说明插入前pParent的平衡因子一定为0,插入后被更新成正负1, 此
		时以pParent为根的树的高度增加,需要继续向上更新
		3. 如果pParent的平衡因子为正负2,则pParent的平衡因子违反平衡树的性质,需要对其进行旋转处理
		*/
		while (pParent)
		{
			// 更新双亲的平衡因子
			if (pCur == pParent->_pLeft)
				pParent->_bf--;
			else
				pParent->_bf++;
			// 更新后检测双亲的平衡因子
			if (0 == pParent->_bf)
				break;
			else if (1 == pParent->_bf || -1 == pParent->_bf)
			{
				// 插入前双亲的平衡因子是0,插入后双亲的平衡因为为1 或者 -1 ,说明以双亲为根的二叉
				// 树的高度增加了一层,因此需要继续向上调整
				pCur = pParent;
				pParent = pCur->_pParent;
			}
			else
			{
				// 双亲的平衡因子为正负2,违反了AVL树的平衡性,需要对以pParent
				// 为根的树进行旋转处理
				if (2 == pParent->_bf && pCur->_bf == 1)
				{
					RotateL(pParent);
				}
				else if (2 == pParent->_bf && pCur->_bf == -1)
				{
					RotateRL(pParent);
				}
				else if (-2 == pParent->_bf && pCur->_bf == 1)
				{
					RotateLR(pParent);
				}
				else 
				{
					RotateR(pParent);
				}
			}
		}
		return true;
	}

    //中序
	void _InOrder(pNode root)
	{
		if (root)
		{
			_InOrder(root->_left);
			cout << root->_value << " ";
			_InOrder(root->_right);
		}
	}
	
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	int _Height(PNode root)
	{
		if (root == nullptr)
			return 0;
		int left = _Height(root->_left);
		int right = _Height(root->_right);
		return left > right ? left + 1 : right + 1;
	}

    //判断平衡
	bool _IsBalanceTree(pNode root)
	{
		// 空树也是AVL树
		if (nullptr == root) 
			return true;

		// 计算root节点的平衡因子:即root左右子树的高度差
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		int diff = rightHeight - leftHeight;
		// 如果计算出的平衡因子与root的平衡因子不相等,或者
		// root平衡因子的绝对值超过1,则一定不是AVL树
		if (diff != root->_bf || (diff > 1 || diff < -1))
			return false;
		// pRoot的左和右如果都是AVL树,则该树一定是AVL树
		return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
	}

private:
	pNode _root = nullptr;
};

void test()
{
	AVLTree<int>* avl = new AVLTree<int>();
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		avl->insert(arr[i]);
	}
}

int main()
{
	test();
	return 0;
}

AVL 树优缺点分析

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

发布了60 篇原创文章 · 获赞 122 · 访问量 8764

猜你喜欢

转载自blog.csdn.net/qq_44759710/article/details/104561926