C++进阶 —— AVL树

目录

一,AVL树     

二,AVL树的旋转

LL(右单旋)

RR(左单旋)

LR(先左单旋再右单旋)

RL(先右单旋再左单旋)

三,AVL树的验证及删除

AVL树的验证

AVL树的删除(了解)

四,AVL树的性能


        关联式容器set/multiset、map/multimap的底层都是按照二叉搜索树实现的,但二叉搜索树其本身是有缺陷的;如插入的元素是有序或近似有序的,二叉搜索树就会退化为单支树,实际复杂度为O(N);

一,AVL树     

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

  • 如一颗二叉搜索树是高度平衡的,则称为AVL树(Adelson-Velsky and Landis);
  • 有n个节点的AVL树,高度可保持在logN,搜索时间复杂度O(logN);

AVL树(平衡二叉树Self-balancing binary search tree)

  • 或是一颗空树;
  • 或是具有左右子树都是AVL树,且左右子树高度差(简称平衡因子)绝对值不超过1;

平衡因子BF(Balance Factor)

  • 左子树高度减去右子树高度的值;平衡二叉树BF取值范围[-1,1],如某节点BF值超过范围就需调整;

C++进阶 —— AVL树_第1张图片

//AVL树节点的定义
template 
struct AVLTreeNode
{
    AVLTreeNode(const T& data)
        :_pLeft(nullptr)
        ,_pRight(nullptr)
        ,_pParent(nullptr)
        ,_data(data)
        ,_bf(0)
    {}
    
    AVLTreeNode* _pLeft; //当前节点的左孩子
    AVLTreeNode* _pRight; //当前节点的右孩子
    AVLTreeNode* _pParent; //当前节点的双亲
    T _data;
    int _bf; //平衡因子
}

二,AVL树的旋转

        AVL树在二叉搜索树的基础上引入了平衡因子,也可看成是二叉搜索树;在一颗平衡的AVL树中插入一个新节点,造成不平衡,就需调整结构使之平衡;

AVL树的插入过程分为两步:

  • 先按照二叉搜索树的方式插入新节点;
  • 在调整节点的平衡因子;

        根据节点插入的位置不同,AVL树的旋转可分为四种:LL(右单旋)、RR(左单旋)、LR(先左单旋再右单旋)、RL(先右单旋再左单旋);

LL(右单旋)

C++进阶 —— AVL树_第2张图片

void _RotateR(pNode pParent)
{
	pNode pSubL = pParent->_pLeft; //pParent左孩子
	pNode pSubLR = pSubL->_pRight; //pParent左孩子的右孩子

	pParent->_pLeft = pSubLR;
    //若pSubLR存在
	if (pSubLR)
		pSubLR->_pParent = pParent;

	pSubL->_pRight = pParent;

    //若pParent可能是子树,存在双亲节点
	pNode pPParent = pParent->_pParent;

	pParent->_pParent = pSubL;
	pSubL->_pParent = pPParent;
	
	if (pPParent == NULL)
	{
		_pRoot = pSubL;
		pSubL->_pParent = NULL;
	}
	else
	{
        //pParent可能是左子树,也可能是右子树
		if (pPParent->_pLeft == pParent)
			pPParent->_pLeft = pSubL;
		else
			pPParent->_pRight = pSubL;
	}

	//更新部分节点的平衡因子
	pParent->_bf = pSubL->_bf = 0;
}

RR(左单旋)

C++进阶 —— AVL树_第3张图片

void _RotateL(pNode pParent)
{
	pNode pSubR = pParent->_pRight; //pParent右孩子
	pNode pSubRL = pSubR->_pLeft; //pParent右孩子的左孩子

	pParent->_pRight = pSubRL;
    //若pSubRL存在
	if (pSubRL)
		pSubRL->_pParent = pParent;

	pSubR->_pLeft = pParent;

    //若pParent可能是子树,存在双亲节点
	pNode pPParent = pParent->_pParent;

	pParent->_pParent = pSubR;
	pSubR->_pParent = pPParent;
	
	if (pPParent == NULL)
	{
		_pRoot = pSubR;
		pSubR->_pParent = NULL;
	}
	else
	{
        //pParent可能是左子树,也可能是右子树
		if (pPParent->_pLeft == pParent)
			pPParent->_pLeft = pSubR;
		else
			pPParent->_pRight = pSubR;
	}

	//更新部分节点的平衡因子
	pParent->_bf = pSubL->_bf = 0;
}

LR(先左单旋再右单旋)

C++进阶 —— AVL树_第4张图片

void _RotateLR(pNode pParent)
{
	pNode pSubL = pParent->_pLeft; 
	pNode pSubLR = pSubL->_pRight; 

	int bf = pSubLR->_bf;

	_RotateL(pParent->_pLeft);

	_RotateR(pParent);
	
	if (bf == 1)
		pSubL->_bf = -1;
	else if (bf == -1)
		pParent->_bf = 1;
}

RL(先右单旋再左单旋)

C++进阶 —— AVL树_第5张图片

void _RotateLR(pNode pParent)
{
	pNode pSubR = pParent->_pRight; 
	pNode pSubRL = pSubR->_pLeft; 

	int bf = pSubRL->_bf;

	_RotateR(pParent->_pRight);

	_RotateL(pParent);
	
	if (bf == -1)
		pSubR->_bf = 1;
	else if (bf == 1)
		pParent->_bf = -1;
}

注:pParent为根的子树不平衡

  • pParent的平衡因子为2,说明pParent右子树高,设此右子树根为pSubR;
    • 单pSubR平衡因子为1,执行左单旋;
    • 单pSubR平衡因子为-1,先执行右单旋,在执行左单旋;
  • pParent的平衡因子为-2,说明pParent左子树高,设此左子树根为pSubL;
    • 单pSubL平衡因子为1,先执行左单旋,在执行右单旋;
    • 单pSubL平衡因子为-1,执行右单旋;

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

三,AVL树的验证及删除

AVL树的验证

 AVL树是在二叉搜索树的基础上加入了平衡限制,要验证AVL树,可分为两步;

  • 验证其为二叉搜索树;
    • 如中序遍历得到一个有序序列,则说明为二叉搜索树;
  • 验证其为平衡树;
    • 每个节点子树高度差绝对值不超过1;
    • 节点的平衡因子是否计算正确;
int _Height(pNode pRoot);
bool _IsBalanceTree(pNode pRoot)
{
	if (pRoot == nullptr)
		return true;

	int leftHeight = _Height(pRoot->_pLeft);
	int rightHeight = _Height(pRoot->_pRight);
	int diff = rightHeight - leftHeight;

	if (diff != pRoot->_bf || (diff > 1 || diff < -1))
		return false;

	return _IsBalanceTree(pRoot->_pLeft) && _IsBalanceTree(pRoot->_pRight);
}

AVL树的删除(了解)

因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点进行删除。

然后在更新平衡因子,删除节点后的平衡因子更新,最差情况下一直要调整到根节点为止;

注:可参考《算法导论》或《数据结构-用面向对象方法与C++描述》;

四,AVL树的性能

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

你可能感兴趣的:(#,C++,编程语言,c++)