深入解析C++中的AVL树:从理论到代码实现(内附源码)

1. AVL树的基本概念与原理

AVL树是一种自平衡的二叉搜索树,它以两位发明者G.M. Adelson-Velsky和E.M. Landis的名字命名。其核心特性是每个节点的左右子树的高度差(也称为平衡因子)不能超过1。当插入或删除节点时,AVL树会通过旋转操作来维持这一平衡,从而确保在最坏情况下,树的高度依然保持在O(log n),从而使搜索、插入和删除操作的时间复杂度都能保持在O(log n)以内。

平衡因子是AVL树中的关键概念。对于每个节点,其平衡因子定义为左子树的高度减去右子树的高度。因此,平衡因子的值只能是-1、0或1。当节点的平衡因子超出这个范围时,AVL树会通过旋转操作恢复平衡。

二叉搜索树(BST)的特性是每个节点的左子节点都比该节点小,右子节点都比该节点大。然而,普通的二叉搜索树在节点插入时可能会导致高度不平衡,例如,如果按顺序插入一系列递增的元素,树可能会退化成一条链表。AVL树通过严格的平衡条件避免了这种情况,因此具有更好的性能。

例如,在一个二叉搜索树中,节点的分布可能非常不均匀,某一侧的子树过深,会导致某些操作的效率降低。而AVL树通过平衡左右子树的高度差,确保树的高度保持在O(log n)级别,从而保证操作的高效性。

在AVL树中,每次插入或删除节点后,都会通过递归地计算每个祖先节点的平衡因子,并对那些不平衡的节点进行旋转修正。根据不平衡的情况,有四种不同的旋转方式:左旋、右旋、左右双旋和右左双旋。下一节将详细讨论这些旋转操作。

1.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; //balance factor
	AVLTreeNode(const pair<K, V>& _kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(_kv)
		, _bf(0)
	{}
};

1.2 AVL树的插入

AVL 树就是在二叉搜索树的基础上引入了平衡因子,因此 AVL 树也可以看成是二叉搜索树。那么
AVL 树的插入过程可以分为两步:
1. 按照二叉搜索树的方式插入新节点
2. 调整节点的平衡因子

在插入节点时,首先需要按照二叉搜索树(BST)的规则找到插入位置。然后,通过向上追溯父节点,逐步调整平衡因子,并在需要时进行旋转修复。以下是插入的详细步骤:

  • 从根节点开始,通过键值大小比较,决定是向左子树还是右子树递归插入节点。
  • 一旦找到合适的插入位置,将新节点插入树中,同时更新其父节点的指针。
  • 接下来,从插入的节点开始,向上追溯,更新每个祖先节点的平衡因子。如果平衡因子变为2或-2,表明该节点的子树出现不平衡,需要进行旋转操作来恢复平衡。

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;
    
    // 向上更新平衡因子并判断是否需要旋转
    while (parent) {
        if (cur == parent->_left) {
            parent->_bf--;  // 左子树增加,平衡因子减小
        } else {
            parent->_bf++;  // 右子树增加,平衡因子增加
        }
        
        // 如果父节点平衡因子为0,树依然平衡,停止更新
        if (parent->_bf == 0) {
            break;
        } else if (parent->_bf == 1 || parent->_bf == -1) {
            // 继续向上更新
            cur = parent;
            parent = parent->_parent;
        } else if (parent->_bf == 2 || parent->_bf == -2) {
            // 出现不平衡,需要进行旋转
            if (parent->_bf == -2 && cur->_bf == -1) {
                RotateR(parent); // 右旋
            } else if (parent->_bf == 2 && cur->_bf == 1) {
                RotateL(parent); // 左旋
            } else if (parent->_bf == 2 && cur->_bf == -1) {
                RotateRL(parent); // 先右后左双旋
            } else if (parent->_bf == -2 && cur->_bf == 1) {
                RotateLR(parent); // 先左后右双旋
            }
            break;
        }
    }
    
    return true;
}

1.3AVL树的旋转

如果在一棵原本是平衡的 AVL 树中插入一个新节点,可能造成不平衡,此时必须调整树的结构,
使之平衡化。根据节点插入位置的不同, AVL 树的旋转分为四种:
1. 新节点插入较高左子树的左侧 --- 左左:右单旋

上图在插入前, AVL 树是平衡的,新节点插入到 30 的左子树 ( 注意:此处不是左孩子 ) 中, 30
子树增加
  了一层,导致以 60 为根的二叉树不平衡,要让 60 平衡,只能将 60 左子树的高度减少一层,右子
树增加一层,
  即将左子树往上提,这样 60 转下来,因为 60 30 大,只能将其放在 30 的右子树,而如果 30
右子树,右子树根的值一定大于 30 ,小于 60 ,只能将其放在 60 的左子树,旋转完成后,更新节点
的平衡因子即可。在旋转过程中,有以下几种情况需要考虑:
  1. 30 节点的右孩子可能存在,也可能不存在
  2. 60 可能是根节点,也可能是子树
    如果是根节点,旋转完成后,要更新根节点
    如果是子树,可能是某个节点的左子树,也可能是右子树
2. 新节点插入较高右子树的右侧 --- 右右:左单旋

3. 新节点插入较高左子树的右侧---左右:先左单旋再右单旋  

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

4. 新节点插入较高右子树的左侧---右左:先右单旋再左单旋 

参考右左双旋。
总结:
假如以 pParent 为根的子树不平衡,即 pParent 的平衡因子为 2 或者 -2 ,分以下情况考虑
1. pParent 的平衡因子为 2 ,说明 pParent 的右子树高,设 pParent 的右子树的根为 pSubR
pSubR 的平衡因子为 1 时,执行左单旋
pSubR 的平衡因子为 -1 时,执行右左双旋
2. pParent 的平衡因子为 -2 ,说明 pParent 的左子树高,设 pParent 的左子树的根为 pSubL
pSubL 的平衡因子为 -1 是,执行右单旋
pSubL 的平衡因子为 1 时,执行左右双旋
旋转完成后,原 pParent 为根的子树个高度降低,已经平衡,不需要再向上更新。

 2. AVL树的时间复杂度与性能分析

1. AVL树的时间复杂度

AVL树是一种自平衡的二叉搜索树(BST),通过在插入和删除节点时进行旋转操作,确保树的高度始终保持在O(log n)的级别。因此,AVL树在最坏情况下的时间复杂度与完全平衡的二叉搜索树相同。我们具体来看各类操作的时间复杂度。

1.1 查找操作的时间复杂度

在AVL树中,查找操作与普通的二叉搜索树相同,都是基于键值的大小,递归地或迭代地沿着树从根节点向下查找目标节点。由于AVL树通过自平衡特性,始终保证树的高度为O(log n),因此查找操作的时间复杂度为:

  • 查找时间复杂度:O(log n)

不论是在最坏情况下,还是平均情况下,AVL树的查找操作时间复杂度都能够保持在O(log n),这是因为AVL树严格限制了树的高度不会退化为线性结构。

1.2 插入操作的时间复杂度

AVL树的插入操作分为两步:首先按照二叉搜索树的规则找到插入位置,其次是向上更新平衡因子并根据需要进行旋转操作。寻找插入位置的过程与查找操作相同,时间复杂度为O(log n)。旋转修复最多涉及O(1)次旋转,因此插入操作的时间复杂度为:

  • 插入时间复杂度:O(log n)

由于旋转操作仅涉及局部调整,不需要遍历整棵树,因此即便需要进行旋转,插入操作的时间复杂度也保持在O(log n)。

1.3 删除操作的时间复杂度

AVL树的删除操作与插入操作类似,首先按照二叉搜索树的规则找到并删除目标节点,然后向上更新平衡因子并执行旋转操作。由于删除操作可能影响多个祖先节点,需要逐层更新平衡因子并进行旋转修复,但旋转操作依旧是局部的,最多进行O(1)次旋转,因此删除操作的时间复杂度也为:

  • 删除时间复杂度:O(log n)

总体来看,AVL树的查找、插入和删除操作的时间复杂度在最坏情况下都能够维持在O(log n),这是因为AVL树通过旋转操作控制了树的高度,防止其退化成线性结构。

2. AVL树的性能分析
2.1 AVL树的平衡性优势

相比于普通的二叉搜索树(BST),AVL树的最大优势在于其自平衡特性。普通BST在插入元素时,容易出现高度不平衡的情况,尤其是在顺序插入元素时,树可能会退化成链表结构,从而导致操作的时间复杂度退化为O(n)。而AVL树通过严格的平衡因子约束,保证了树的高度总是接近O(log n),大大提高了查找、插入和删除操作的效率。

例如,在普通BST中,如果连续插入递增的元素,树可能会变成如下形态:

1
 \
  2
   \
    3
     \
      4
       \
        5
 

在这种情况下,查找元素5的时间复杂度为O(n),性能急剧下降。而在AVL树中,即便插入顺序元素,树也会通过旋转保持平衡,避免退化成线性结构。

2.2 AVL树的空间开销

虽然AVL树的平衡性带来了时间复杂度的优势,但它也付出了一定的空间开销。每个节点除了需要存储键值对外,还需要额外存储一个平衡因子(_bf),用于表示节点的平衡状态。相比于普通的二叉搜索树,AVL树的每个节点都增加了一个额外的整数字段,用于维护平衡因子。

然而,相比于AVL树带来的性能提升,这部分额外的空间开销是非常小的,尤其在大多数应用场景下,AVL树的平衡性能够大幅提升整体系统的性能。

2.3 AVL树与其他自平衡树的比较

除了AVL树,其他常见的自平衡二叉树包括红黑树(Red-Black Tree)和Splay树。与这些树相比,AVL树具有以下特点:

  • AVL树 vs 红黑树
    • AVL树比红黑树更“严格”地保持平衡,因此查找操作的时间复杂度通常比红黑树更优。
    • 然而,由于AVL树的旋转次数比红黑树更多,因此在频繁插入和删除操作的场景下,红黑树可能表现更优,因为红黑树的旋转操作较少。
  • AVL树 vs Splay树
    • Splay树是一种通过“自我调整”的方式来保持平衡的树结构。与AVL树不同,Splay树在每次查找、插入和删除操作时,都会通过一系列旋转将最近访问的节点移动到根节点,因此在局部性强的操作场景中,Splay树的性能较好。
    • 然而,在全局性能上,AVL树的时间复杂度更稳定,适用于更广泛的应用场景。
3. AVL树的适用场景

由于AVL树的平衡性,它非常适合需要频繁查找操作且对性能要求较高的场景。常见的应用场景包括:

  • 数据库索引:AVL树常用于实现数据库中的索引结构,能够在大规模数据中提供快速的查找和插入。
  • 内存管理:在内存管理系统中,AVL树可以用来维护空闲内存块的列表,从而实现高效的内存分配和回收操作。
  • 字典与映射:AVL树可以用来实现字典和映射,支持键值对的快速查找、插入和删除操作。

总之,AVL树通过严格的自平衡机制,保证了树的高度始终保持在O(log n),从而在大多数查找密集型的应用中,能够提供优异的性能表现。

以上就是AVL树的全部啦!希望对你有所帮助,下面是我用K,V模板完成的完整的AVL树代码,希望对你有些帮助!

#define _CRT_SECURE_NO_WARNINGS 1
#include <assert.h>
#include <iostream>
using namespace std;
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; //balance factor
	AVLTreeNode(const pair<K, V>& _kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(_kv)
		, _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;
		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;//每次插入都和父亲链接上
		//现在每次插入要更新平衡因子

		while (parent)
		{
			if (cur == parent->_left) {
				parent->_bf--;
			}
			else {
				parent->_bf++;
			}
			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1) {
				//继续向上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2) {
				//当前子树出问题了
				if (parent->_bf == -2 && cur->_bf == -1)
				{
					RotateR(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == 1)
				{
					RotateL(parent);
				}
				else if (parent->_bf == 2 && cur->_bf == -1)
				{
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					RotateLR(parent);
				}
				break;
			}
			else
			{
				assert(false);
			}
		}


		return true;
	}


	
	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_kv.first < key)
			{
				cur = cur->_right;
			}
			else if (cur->_kv.first > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return nullptr;
	}

	

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		parent->_left = subLR;
		if(subLR)
		subLR->_parent = parent;
		subL->_right = parent;

		Node* ppNode = parent->_parent;
		parent->_parent = subL;
		if (parent == _root) {
			_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 RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		subR->_left = parent;

		Node* ppNode = parent->_parent;
		parent->_parent = subR;
		if (parent == _root) {
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppNode->_left == parent) {
				ppNode->_left = subR;
			}
			else
			{
				ppNode->_right = subR;
			}
			subR->_parent = ppNode;
			
		}
		parent->_bf = subR->_bf = 0;
		
	}
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;
		RotateL(parent->_left);
		RotateR(parent);
		//单旋结束后平衡因子会记为0,实际上平衡因子会改变
		if (bf == -1)//代表在左边插入
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		else if (bf == 1)
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == 0)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 0;
		}
		else {
			assert(false);
			//理论上不可能,万一写错了呢
		}
	}
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;


		RotateR(subR);
		RotateL(parent);
		if (bf == -1)
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 1)
		{
			parent->_bf = -1;
			subRL->_bf = 0;
			subR->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			subRL->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}
	void InOrder() {
		_InOrder(_root);
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}
private:
	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;
		}
		return max(_Height(root->_left), _Height(root->_right)) + 1;
	}
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		if (abs(leftHeight - rightHeight) >= 2)
			return false;
		return _IsBalance(root->_left) && _IsBalance(root->_right);
	}
private:
	Node* _root = nullptr;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hqxnb666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值