数据结构之AVL树(C++实现)

数据结构之AVL树(C++)

AVL树是二叉搜索平衡树,在二叉搜索树的基础上添加了平衡,也就是确保每个结点的左右子树高度差的绝对值不超过1。

我之前的博客已经介绍过了二叉搜索树的基本概念和简单实现,具体参考数据结构之二叉搜索树(C++实现)

1 AVL树的概念

我们知道二叉搜索树虽然可以缩短查找的效率,但是数据有序或者接近有序的情况下,二叉搜索树回退化成单支树,查找元素相当于在顺序表中遍历查找,效率低下。

AVL树是一种二叉搜索平衡树,它可以是一颗空树或者满足以下性质的二叉搜索树

  • 左右子树高度之差(平衡因子)的绝对值不超过1。
  • 它的左右子树的都是AVL树。

img

2. AVL树的实现

2.1 AVL树的定义
template<class K, class V>
struct AVLTreeNode
{
	AVLTreeNode<K, V>* _left;    // 左孩子
	AVLTreeNode<K, V>* _right;  // 右孩子
	AVLTreeNode<K, V>* _parent; // 双亲

	pair<K, V> _kv;				// 存储键值对
	int _bf;  						// 平衡因子

	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};
2.2 AVL树的插入

AVL树的插入是在二叉搜索树的基础上引入了平衡因子,插入过程分为两步:

  1. 按照二叉搜索树插入新节点。

  2. 调节结点的平衡因子。

重点介绍二叉平衡搜索树如何进行平衡因子调节,在插入之前,父节点的平衡因子有三种情况:-1,0,1,分一下两种情况:

  1. 如果cur插入到parent的左侧,只需要给parent的平衡因子-1即可。
  2. 如果cur插入到parent的右侧,只需要给parent的平衡因子+1即可。

此时,parent平衡因子可能有三种情况,0,正负1,正负2。

  1. 如果parent平衡因子为0,说明之前的平衡因子为正负1,调整之后依然满足AVL树的特性。
  2. 如果parent平衡因子为正负1,说明插入之前的平衡因子为0,插入之后,会导致以parent为根的数高度增加,那么会影响parent的祖先结点的平衡因子,因此,需要继续向上更新,直到parent==root
  3. 如果平衡因子为正负2,已经不满足平衡树的性质,需要做旋转处理。

AVL树针对不同情况,需要做的旋转也不同,主要是分为四种。

  1. 新节点插入较高左子树的左侧,执行右单旋。
    在这里插入图片描述

    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 (_root == parent)
        {
            _root = subL;
            subL->_parent = nullptr;
        }
        else
        {
            if (ppNode->_left == _parent)
            {
                ppNode->_left = subL;
            }
            else
            {
                ppNode->_right = subL;
            }
    
            subL->_parent = ppNode;
        }
    
        subL->_bf = parent->_bf = 0;
    }
    
  2. 新节点插入较高右子树的右侧,执行左单旋。

在这里插入图片描述

void RotateL(Node* parent)
{
 	Node* subR = parent->_right;
 	Node* subRL = subR->_left;
  
 	parent->_right = subRL;
 	if (subRL)
 		subRL->_parent = 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;
}
  1. 新节点插入较高左子树的右侧,先执行左单旋,再右单旋。

在这里插入图片描述

void RotateLR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    int bf = subLR->_bf;

    RotateL(parent->_left);
    RotateR(parent);

    subLR->_bf = 0;
    if (bf == 1)
    {
        parent->_bf = 0;
        subL->_bf = -1;
    }
    else if (bf == -1)
    {
        parent->_bf = 0;
        subL->_bf = 1;
    }
    else if (bf == 0)
    {
        parent->_bf = 0;
        subL->_bf = 0;
    }
    else
    {
    	assert(false);
    }
}
  1. 新节点插入较高右子树的左侧,先执行右单旋,在左单旋。(参考左右双旋)

    void RotateRL(Node* parent)
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
    
        int bf = subRL->_bf;
    
        RotateR(parent->_right);
        RotateL(parent);
    
        subRL->_bf = 0;
        if (bf == 1)
        {
            subR->_bf = 0;
            parent->_bf = -1;
        }
        else if (bf == -1)
        {
            subR->_bf = 1;
            parent->_bf = 0;
        }
        else if (bf == 0)
        {
            parent->_bf = 0;
            subR->_bf = 0;
        }
        else
        {
        	assert(false);
        }
    }
    

插入代码如下:

bool Insert(const pair<K,V>& kv) 
{
    if (_root == nullptr)
    {
        _root = new Node(kv);
        return true;
    }

    Node* parent = nullptr;
    Node* cur = _root;
    while (cur)
    {
        if (cur->_kv.first < kv.first)
        {
            parent = cur;
            cur = cur->_right;
        }
        else if (cur->_kv.first > kv.first)
        {
            parent = cur;
            cur = cur->_left;
        }
        else
        {
            return false;
        }
    }

    cur = new Node(kv);
    if (parent->_kv.first < kv.first)
    {
        parent->_right = cur;
    }
    else
    {
        parent->_left = cur;
    }

    cur->_parent = parent;

    // 控制平衡
    // 1、更新平衡因子
    while (parent)
    {
        if (cur == parent->_right)
        {
            parent->_bf++;
        }
        else
        {
            parent->_bf--;
        }

        if (parent->_bf == 0)
        {
            break;
        }
        else if (abs(parent->_bf) == 1)
        {
            parent = parent->_parent;
            cur = cur->_parent;
        }
        else if (abs(parent->_bf) == 2)
        {
            // 说明parent所在子树已经不平衡了,需要旋转处理
            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;
        }
        else
        {
            assert(false);
        }
    }

    return true;
}
2.3 AVL树的验证

AVL树是在二叉搜索树的基础上做了平衡性的调整,因此,我们需要对二叉搜索树进行平衡判断。可以分为两步:

  1. 验证是否为二叉搜索树。如果中序遍历得到的是一个有序的值,那么说明是二叉搜索树。
  2. 验证是否为平衡树。每个结点的子树高度差是否不超过1或者判断每个结点的平衡因子是否是-1、1、0这三种值。
// 1. 中序遍历
void Inorder()
    {
        _InOrder(_root);
        cout << endl;
    }

// 2. 平衡判断
bool IsBalance()
{
    _IsBalance(_root);
}

bool _IsBalance(Node *root)
{
    // 空树满足AVL树
    if(root == nullptr)
        return true;
    
    // 左子树高度
    int leftHT = Height(root->_left);
    // 右子树高度
    int rightHT = Height(root->_right);
    // 左右子树高度差
    int diff = rightHT - leftHT;
	
    // 左右子树的高度差不等于平衡因子
    if(diff != root->_bf) {
        cout << root->_kv.first << "平衡因子异常" << endl;
        return false;
    }
    // 递归调用,如果平衡因子的绝对值小于2并且左子树和右子树都是AVL树。
    return abs(diff) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);
}

int Height(Node* root)
{
    if(root == nullptr)
        return 0;
    int leftHT = Height(root->_left);
    int rightHT = Height(root->_right);

    return max(leftHT,rightHT) + 1;
}

void _InOrder(Node* root)
{
    if (root == nullptr)
        return;

    _InOrder(root->_left);
    cout << root->_kv.first << ":" << root->_kv.second << endl;
    _InOrder(root->_right);
}
2.4 AVL树的删除(了解)

AVL树也是一种二叉搜索树,按照二叉搜索树的方式将结点删除,然后更新平衡因子,具体可以参考《算法导论》或者《数据结构-用面向对象方式与C++描述》这两本书。

3. AVL树的性能

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值