数据结构进阶——AVL树

news2024/11/23 11:55:06

数据结构进阶——AVL树

  • 0. 前言
  • 1. AVL树的概念
  • 2. AVL树节点,和树的定义
  • 3. AVL树的插入
  • 4. AVL树的旋转
  • 5. AVL树的验证
  • 6. AVL树的删除(了解)
  • 7. AVL树实现完整代码
  • 8. AVL树的性能


0. 前言


学习本章,需要大家先掌握搜索二叉树,了解键值对pair

  • 学习搜索二叉树点击此处:https://blog.csdn.net/weixin_73870552/article/details/138686066?spm=1001.2014.3001.5501

1. AVL树的概念


1. 搜索二叉树的弊端:

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

2. AVL树的性质:

在这里插入图片描述

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

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


2. AVL树节点,和树的定义


1. 节点:

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)
	{}
};
  • 该节点的定义是一个三叉链,_left_right分别指向左右子树,_parent指向父节点;
  • 节点中存储的有效数据为pair<K, V>,类型的键值对;
  • _bf为平衡因子,在此我们定义为右树高度减去左树高度,用来控制左右子树的高度(注意:平衡因子只是其中一种控制平衡的手段,并不是唯一的);
  • 定义了一个模版构造函数,以便后续使用。

2. 树:

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	...

private:
	Node* _root = nullptr;
	size_t _size = 0;
};
  • _size记录树中一共有多少数据(多少节点)。

3. AVL树的插入


1. 思路:

  • AVL树就是在搜索二叉树的基础上引入了平衡因子,因此AVL树也可以看成是搜索二叉树。那么AVL树的插入过程可以分为两步:
    • 按照二叉搜索树的方式插入新节点;
    • 调整节点的平衡因子。
  • 调整平衡因子的过程,又可以细分为两部分:
    • 平衡因子的更新;
    • 节点的调整(旋转)+ 平衡因子的更新。

1. 模拟插入过程(红色节点表示新插入节点):

在这里插入图片描述

  • 插入新节点后,第一件事,是更新该新节点的父亲的平衡因子:
    • 新增节点在左,父亲bf--
    • 新增节点在右,父亲bf++
  • 更新完该新节点的父亲后,还要判断是否需要往上更新(新增节点可能会影响祖先,但是一定不会影响兄弟):
    • 更新后,父亲bf == 0,父亲所在的子树高度不变,不用再继续往上更新了,插入结束。 (ps:在插入节点前,父亲bf == 1 or -1,子树一边高,一边低,新插入节点填补低的那边
    • 更新后,父亲bf == 1 or -1,父亲所在的子树高度变了,需要继续往上更新。(ps:在插入节点前,父亲bf == 0,两边一样高,插入新节点导致高度变化
    • 更新后,父亲bf == 2 or -2,父亲所在的子树不平衡,需要旋转调整。
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);
			_size++;
			return true;
		}

		// 通过parent向上找父节点
		Node* parent = nullptr;
		Node* cur = _root;

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

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

		_size++;
		
		// 调整,AVL树的核心部分

		while (parent)
		{
			// 平衡因子的更新
			if (cur == parent->_left)
			{
				// 插入在左节点,_bf--
				parent->_bf--;
			}
			else
			{
				// 插入在右节点,_bf++
				parent->_bf++;
			}
			
			// 判断是否要继续向上更新,和是否旋转调整
			if (parent->_bf == 0)
			{
				// 更新后 _bf == 0,说明子树高度不变,不需要往上更新
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				// 更新后子树高度改变,需要往上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 旋转调整
				...
			}
			else
			{
				// 平衡因子绝对值大于2,直接报错
				assert(false);
			}
		}

		return true;
	}

	...

private:
	Node* _root = nullptr;
	size_t _size = 0;
};

4. AVL树的旋转

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

1. 新节点插入较高左子树的左侧—右右:左单旋

  • 先考虑一种最简单的场景(场景1):

    • 直接将8节点链接到9节点的左边即可。
      在这里插入图片描述
  • 再看一种更复杂的场景(场景2):

    • 需要将13节点链接到9节点的右子树,将9节点链接到15节点的左子树。
      在这里插入图片描述
  • 还有更加复杂的情况,但是我们可以对所有的情况进行一个归类,画出抽象图:

    • a,b,c 都是高度为h的AVL平衡树;
    • 只需要将b框中的节点(subRL),链接到30节点(parent)的右子树(将60节点的左子树链接到30节点的右边);再将30节点(parent),连接到60节点(subR)的左子树即可。(别忘了是三叉链,要同步更新父节点)
    • 旋转完后别忘了更新平衡因子,左单旋后,parentsubR节点的_bf都为0,其余节点的平衡因子均不会受到印象。
    • h = 0时,就对应场景1的情况;h = 1时,就对应场景2的情况。

在这里插入图片描述

  • 代码实现:
    • 注意,在更新subRL节点的父节点时,需要先判断subRL是否为空(h=0的情况),如果为空,就不要访问subRL了;
    • 还需要记录parent的父节点,方便旋转后将subR链接给parent->_parent,和整棵树链接起来;
    • parent就是根节点时,需要特殊处理,要更新根节点为subR
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	// 左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		// 更新左右节点
		parent->_right = subRL;
		subR->_left = parent;

		// 提前记录parent的parent
		Node* parentParent = parent->_parent;

		// 更新_parent
		parent->_parent = subR;
		if (subRL)	// 判断为不为空,subRL是唯一一个可能为空的节点
		{
			subRL->_parent = parent;
		}

		// 处理根
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else if (parentParent->_left == parent)
		{
			parentParent->_left = subR;
			subR->_parent = parentParent;
		}
		else
		{
			parentParent->_right = subR;
			subR->_parent = parentParent;
		}

		// 更新平衡因子
		parent->_bf = subR->_bf = 0;
	}

	...

private:
	Node* _root = nullptr;
	size_t _size = 0;
};

2. 新节点插入较高左子树的左侧—左左:右单旋

  • 这里不带着大家一步一步分析了,直接上抽象图:
    • a,b,c 均为高度为h的AVL平衡树;
    • 只需要将subLRparent的左,再将parentsubL的右即可;
    • 随后更新平衡因子,subLparent_bf都更新为0。

在这里插入图片描述

  • 类比左单旋,直接上代码:
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	// 右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		// 更新左右节点
		subL->_right = parent;
		parent->_left = subLR;

		Node* parentParent = parent->_parent;

		// 更新_parent
		parent->_parent = subL;
		if (subLR)
		{
			subLR->_parent = parent;
		}

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else if (parentParent->_left == parent)
		{
			parentParent->_left = subL;
			subL->_parent = parentParent;
		}
		else
		{
			parentParent->_right = subL;
			subL->_parent = parentParent;
		}
		
		parent->_bf = subL->_bf = 0;
	}

	...

private:
	Node* _root = nullptr;
	size_t _size = 0;
};

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

  • 注意:该抽象图只是左右双旋中的一种情况,为在60的左子树新增。还有两种情况没有画出来,分别是(1)60作为新增节点的情况;(2)在60的右子树新增的情况。不同的情况,旋转后得到的平衡因子有差别。
    在这里插入图片描述

  • h = 0 的情况:60就是新插入节点。此时b,c子树不存在,注意是不存在,连空都不是。此时,parentsubLsubLR的平衡因子均为0。
    在这里插入图片描述

  • h = 1的情况(该情况又分两种):

    • 在60的左子树新增:注意此时subLsubLR的平衡因子均为0,只有parent的平衡因子为1,和h = 0的情况不一样。
      在这里插入图片描述
    • 在60的右子树新增:这种情况最后的到的子树,和上图只有一处区别,就是b变成了NULLc变成了50,故此时subL的平衡因子为-1,parentsubLR的平衡因子均为0。和在60左子树新增的情况,平衡因子又不一样。
  • 代码实现:

    • 我们可以根据新节点插入后,subLR的平衡因子来确定到底是上述哪种情况。(1)subLR->_bf == 0,说明subLR就是新增节点;(2)subLR->_bf == -1,说明是在subLR的左子树新增;(3)subLR->_bf == 1,说明是在subLR的右子树新增。
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	// 左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;	// 提前记录subLR的平衡因子,避免单旋操作后,值丢失

		// 先左旋再右旋
		RotateL(parent->_left);
		RotateR(parent);

		// 更新平衡因子

		if (bf == 0)
		{
			// subLR自己就是新增节点
			subL->_bf = subLR->_bf = parent->_bf = 0;
		}
		else if (bf == -1)
		{
			// subLR的左子树新增
			parent->_bf = 1;
			subL->_bf = subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			// subLR的右子树新增
			parent->_bf = subLR->_bf = 0;
			subL->_bf = -1;
		}
		else
		{
			assert(false);	// 检查点
		}
	}

	...

private:
	Node* _root = nullptr;
	size_t _size = 0;
};

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

  • 注意:该抽象图只是右左双旋中的一种情况,为在60的右子树新增。还有两种情况没有表示出来,分别是(1)60就是新增节点;(2)在60的左子树新增。
    在这里插入图片描述
  • 类比左右双旋,上代码:
    • 还是要注意,可以通过subRL的平衡因子区分上述三种情况。(1)subRL->_bf == 0,说明subRL就是新增节点;(2) subRL->_bf == 1,说明是在subRL的右子树新增,parent->_bf == -1;(3)subRL->_bf == -1,说明是在subRL的左子树新增,subR->_bf == 1
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	// 右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		int bf = subRL->_bf;	// 提前记录subRL的平衡因子,避免单旋操作后,值丢失

		// 先右旋再左旋
		RotateR(parent->_right);
		RotateL(parent);

		// 更新平衡因子
		
		if (bf == 0)
		{
			// subRL自己就是新增节点
			parent->_bf = subR->_bf = subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			// subRL的左子树新增
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if(bf == 1)
		{
			// subRL的右子树新增
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);	// 检查点
		}
	}

	...

private:
	Node* _root = nullptr;
	size_t _size = 0;
};

5. 完善插入:

  • 旋转完成后是不需要继续向上更新平衡因子的,因为(1)旋转让这棵树平衡了;(2)旋转降低了这棵子树的高度,恢复到更插入前一样的高度,所以对上一层没有影响,不用更新。
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);
			_size++;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;

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

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

		_size++;
		
		// 调整,AVL树的核心部分

		while (parent)
		{
			// 平衡因子的更新
			if (cur == parent->_left)
			{
				// 插入在左节点,_bf--
				parent->_bf--;
			}
			else
			{
				// 插入在右节点,_bf++
				parent->_bf++;
			}
			
			// 判断是否要继续向上更新,和是否旋转调整
			if (parent->_bf == 0)
			{
				// 更新后 _bf == 0,说明子树高度不变,不需要往上更新
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				// 更新后子树高度改变,需要往上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 平衡因子绝对值等于2,旋转调整

				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)
				{
					// 先右边高,再左边高,右左双旋
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					// 先左边高,再右边高,左右双旋
					RotateLR(parent);
				}
				else
				{
					assert(false);
				}

				// 完成旋转后,不需要再往上更新平衡因子了,直接break
				//	1、旋转让这棵树平衡了
				//	2、旋转降低了这棵子树的高度,恢复到更插入前一样的高度,所以对上一层没有影响,不用更新
				break;
			}
			else
			{
				// 平衡因子绝对值大于2,直接报错
				assert(false);
			}
		}

		return true;
	}

	...

private:
	Node* _root = nullptr;
	size_t _size = 0;
};

5. AVL树的验证


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

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

2. 验证其为平衡树:

  • 每个节点子树高度差的绝对值不超过1;
  • 节点的平衡因子是否计算正确。

3. 相关函数实现:

template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	int Height()
	{
		return _Height(_root);
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

		return abs(rightHeight - leftHeight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}
	
	size_t Size() const 
	{
		return _size;
	}
	
	...

private:
	Node* _root = nullptr;
	size_t _size = 0;
};

4. 测试代码:

void Test()
{
	const int N = 100;
	srand(time(0));
	vector<int> v;

	for (int i = 0; i < N; i++)
	{
		v.push_back(rand());
	}

	AVLTree<int, int> tree;
	for (auto e : v)
	{
		tree.Insert(make_pair(e, e));
	}
	tree.InOrder();		// 检查是否有序
	cout << tree.IsBalance() << endl;	// 检查是否平衡
	cout << tree.Height() << endl;	// 看看高度
	cout << tree.Size() << endl;	// 看看数据量
}

6. AVL树的删除(了解)


1. 思路:

  • 因为AVL树也是二叉搜索树,可按照二叉搜索树的方式将节点删除,然后再更新平衡因子。只不过,平衡因子更新最差情况下要一直调整到根节点的位置。

2. 例:

在这里插入图片描述

  • 假如我们要删除根节点,可以先在右子树找到替换节点6,进行替换删除,然后更新平衡因子,得到的树如下图所示:

在这里插入图片描述

  • 更新7这个节点的平衡因子,更新为2,不用继续向上更新了,对7节点进行左单旋调整即可。

删除的代码不再写了,面试时也几乎不会考察,最多考察思路。


7. AVL树实现完整代码


#pragma once

#include<iostream>
#include<assert.h>

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);
			_size++;
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;

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

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

		_size++;
		
		// 调整,AVL树的核心部分

		while (parent)
		{
			// 平衡因子的更新
			if (cur == parent->_left)
			{
				// 插入在左节点,_bf--
				parent->_bf--;
			}
			else
			{
				// 插入在右节点,_bf++
				parent->_bf++;
			}
			
			// 判断是否要继续向上更新,和是否旋转调整
			if (parent->_bf == 0)
			{
				// 更新后 _bf == 0,说明子树高度不变,不需要往上更新
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				// 更新后子树高度改变,需要往上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2)
			{
				// 平衡因子绝对值等于2,旋转调整

				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)
				{
					// 先右边高,再左边高,右左双旋
					RotateRL(parent);
				}
				else if (parent->_bf == -2 && cur->_bf == 1)
				{
					// 先左边高,再右边高,左右双旋
					RotateLR(parent);
				}
				else
				{
					assert(false);
				}

				// 完成旋转后,不需要再往上更新平衡因子了,直接break
				//	1、旋转让这棵树平衡了
				//	2、旋转降低了这棵子树的高度,恢复到更插入前一样的高度,所以对上一层没有影响,不用更新
				break;
			}
			else
			{
				// 平衡因子绝对值大于2,直接报错
				assert(false);
			}
		}

		return true;
	}

	// 左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		// 更新左右节点
		parent->_right = subRL;
		subR->_left = parent;

		// 提前记录parent的parent
		Node* parentParent = parent->_parent;

		// 更新_parent
		parent->_parent = subR;
		if (subRL)	// 判断为不为空,subRL是唯一一个可能为空的节点
		{
			subRL->_parent = parent;
		}

		// 处理根
		if (_root == parent)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else if (parentParent->_left == parent)
		{
			parentParent->_left = subR;
			subR->_parent = parentParent;
		}
		else
		{
			parentParent->_right = subR;
			subR->_parent = parentParent;
		}

		// 更新平衡因子
		parent->_bf = subR->_bf = 0;
	}

	// 右单旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		// 更新左右节点
		subL->_right = parent;
		parent->_left = subLR;

		Node* parentParent = parent->_parent;

		// 更新_parent
		parent->_parent = subL;
		if (subLR)
		{
			subLR->_parent = parent;
		}

		if (_root == parent)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else if (parentParent->_left == parent)
		{
			parentParent->_left = subL;
			subL->_parent = parentParent;
		}
		else
		{
			parentParent->_right = subL;
			subL->_parent = parentParent;
		}
		
		parent->_bf = subL->_bf = 0;
	}

	// 右左双旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		int bf = subRL->_bf;	// 提前记录subRL的平衡因子,避免单旋操作后,值丢失

		// 先右旋再左旋
		RotateR(parent->_right);
		RotateL(parent);

		// 更新平衡因子
		
		if (bf == 0)
		{
			// subRL自己就是新增节点
			parent->_bf = subR->_bf = subRL->_bf = 0;
		}
		else if (bf == -1)
		{
			// subRL的左子树新增
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if(bf == 1)
		{
			// subRL的右子树新增
			parent->_bf = -1;
			subR->_bf = 0;
			subRL->_bf = 0;
		}
		else
		{
			assert(false);	// 检查点
		}
	}

	// 左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		int bf = subLR->_bf;	// 提前记录subLR的平衡因子,避免单旋操作后,值丢失

		// 先左旋再右旋
		RotateL(parent->_left);
		RotateR(parent);

		// 更新平衡因子

		if (bf == 0)
		{
			// subLR自己就是新增节点
			subL->_bf = subLR->_bf = parent->_bf = 0;
		}
		else if (bf == -1)
		{
			// subLR的左子树新增
			parent->_bf = 1;
			subL->_bf = subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			// subLR的右子树新增
			parent->_bf = subLR->_bf = 0;
			subL->_bf = -1;
		}
		else
		{
			assert(false);	// 检查点
		}
	}

	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 _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);
		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	int Height()
	{
		return _Height(_root);
	}

	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		if (rightHeight - leftHeight != root->_bf)
		{
			cout << root->_kv.first << "平衡因子异常" << endl;
			return false;
		}

		return abs(rightHeight - leftHeight) < 2
			&& _IsBalance(root->_left)
			&& _IsBalance(root->_right);
	}

	size_t Size() const 
	{
		return _size;
	}

private:
	Node* _root = nullptr;
	size_t _size = 0;
};

8. AVL树的性能


AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询时高效的时间复杂度,即 l o g 2 ( N ) log_2 (N) log2(N)。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:

  • 插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。

因此:如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树。但是一个经常修改的结构,就不太适合用AVL树。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1831789.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

04 远程访问及控制

目录 4.1 SSH远程管理 4.1.1 配置OpenSSH服务器 1. 服务监听选项 2. 用户登录控制 3. 登录验证方式 4.1.2 使用SSH客户端程序 1. 命令程序ssh、scp、sftp 1. ssh远程登录 2. scp远程复制 3. sftp安全FTP 2. 图形工具Xshell 4.1.3 构建密钥对验证的SSH体系 1. 在客户端创建密钥…

Hive笔记-3

3.2.2 查看表 1) 展示所有表 (1) 语法: 语法: SHOW TABLES [IN database_name] LIKE [identifier_with_wildcards]; In database_name 写的是查哪个数据库,一般不写默认是当前数据库 Like 后面跟通配符表达式 (2) 案例: 查看在 db_hive1 数据库里有没有以 stu 开头的表 …

实用软件下载:UltraEditUEStudio最新安装包及详细安装教程

​UEStudio简介&#xff1a;UEStudio建立在上文本编辑器UltraEdit的功能基础上&#xff0c;并为团队和开发人员提供了其他功能&#xff0c;例如深度Git集成&#xff0c;您可以直接在UEStudio中克隆&#xff0c;签出&#xff0c;更新&#xff0c;提交&#xff0c;推入/拉入等操作…

FPGA - 滤波器 - IIR滤波器设计

一&#xff0c;IIR滤波器 在FPGA - 滤波器 - FIR滤波器设计中可知&#xff0c;数字滤波器是一个时域离散系统。任何一个时域离散系统都可以用一个N阶差分方程来表示&#xff0c;即&#xff1a; 式中&#xff0c;x(n)和y(n)分别是系统的输入序列和输出序列&#xff1b;aj和bi均为…

Sermant标签路由能力在同城双活场景的应用

作者&#xff1a;聂子雄 华为云高级软件工程师 摘要&#xff1a;目前应用上云已成为趋势&#xff0c;用户也对应用在云上的高可靠方案有更高追求&#xff0c;目前同城双活场景作为应用高可靠方案中的一种常见实践方案&#xff0c;对微服务流量提出了数据中心亲和性的要求&…

Java_JDK下载与环境变量配置

目录 一、JDK下载安装 二、安装后配置环境变量 三、在编辑器里使用JDK 一、JDK下载安装 JDK 是Java开发工具包&#xff0c;它提供了用于开发和运行Java程序所需的工具和库。JDK包括Java编译器、Java虚拟机、Java标准库等。在IDEA中使用Java语言编写代码时&#xff0c;需要安…

海康视觉算法平台VisionMaster 4.3.0 C# 二次开发01 加载方案并获取结果

前言 第一次使用海康视觉算法平台VisionMaster 4.3.0&#xff0c;项目中要使用这个平台进行视觉处理并获取结果。 运行效果 开发环境 C#&#xff0c; WPF&#xff0c; vs2022, 海康视觉算法平台VisionMaster 4.3.0 基本概念 上图这些.sol为后缀的是vm的方案文件。 打开方案文…

[element-ui]el-select多选选择器选中其中一个选项,不可删除

背景&#xff1a; 产品真的很多奇奇怪怪的需求&#xff0c;一边吐槽一边实现。 前提&#xff1a;选择器作为表格的筛选项&#xff0c;提供三个选项值。 要求&#xff1a;默认选中其中一个值&#xff0c;这个值不可删除。 如图&#xff1a; 小声吐槽&#xff1a;搞这些有什么…

LSS算法核心原理详细解读,一看就懂,不懂请打我!

目录 核心整体流程分步阐述backbone几何关系&#xff08;创建视锥&#xff09;和视锥投影到egoVoxel PoolingHead 总结 核心 将2D图像特征转换到BEV feature特征 该算法是BEV领域中的一大基石 整体流程 流程步骤 &#xff08;1&#xff09;利用backbone获得环视图像&#xf…

Vue路由讲解-05

这里的路由并不是指我们平时所说的硬件路由器&#xff0c;这里的路由就是SPA&#xff08;single page application单页应用&#xff09;的路径管理器。再通俗的说&#xff0c;vue-router就是WebApp的链接路径管理系统。 vue-router是Vue.js官方的路由插件&#xff0c;它和vue.j…

Vue48-ref属性

一、需求&#xff1a;操作DOM元素 1-1、使用原生的id属性 不太好&#xff01; 1-2、使用 ref属性 原生HTML中&#xff0c;用id属性给元素打标识&#xff0c;vue里面用ref属性。 给哪个元素加了ref属性&#xff0c;vc实例对象就收集哪个元素&#xff01;&#xff01;&#xff0…

开放式耳机怎么挑选,个人经验总结快来看!

在选择开放式耳机时&#xff0c;了解一些关键的选购因素和推荐的品牌款式是非常有帮助的。这类耳机允许声音在耳机和外界之间自然流动&#xff0c;提供更自然的听觉体验。它们通常不会完全隔绝外界噪音&#xff0c;适合需要随时留意周围环境的人群&#xff0c;如运动爱好者或需…

AutoMQ 生态集成 CubeFS

CubeFS [1] 是新一代云原生存储产品&#xff0c;目前是云原生计算基金会 CNCF托管的孵化阶段开源项目&#xff0c; 兼容 S3、POSIX、HDFS 等多种访问协议&#xff0c;支持多副本与纠删码两种存储引擎&#xff0c;为用户提供多租户、 多 AZ 部署以及跨区域复制等多种特性&#x…

安徽保安员精选模拟试题(含答案)

1、风险管理的三要素是()&#xff0c;风险评价和风险控制。 A、频率分析 B、风险分析 C、风险转移 D、后果估计 答案:B 2、治安保卫重要部位是指由()确定的、关系本单位生产业务全局的部位和生产环节。 A、企事业重点单位 B、地方政府 C、企事业单位保卫协会 D、公安机关 …

垂直领域大模型微调最全指南

1.概述 一年来多以来&#xff0c;大语言模型发展和变化越来越快&#xff0c;总体呈现出模型尺寸越变越大&#xff0c;算力需求越来越多&#xff0c;模型推理要求越来越高的特点。在这种背景下&#xff0c;现在不同的人关于垂域 LLM 出现了一些争议&#xff0c;一部分人认为随着…

2024.618到底买什么数码值得?带你一起来看看!

在618期间&#xff0c;这些新品可能会有特别的优惠活动&#xff0c;包括但不限于折扣、满减、赠品等。因此&#xff0c;如果你正在寻找一款适合自己的数码产品&#xff0c;不妨关注各大电商平台的618促销活动&#xff0c;把握机会&#xff0c;以优惠的价格购买到心仪的产品。 …

Windows采用txt和bat来一次性建立多个文件夹

前言 最近工作需要一次性建立多个文件夹&#xff0c;方便保存不同的数据&#xff0c;所以在网上搜了搜方法&#xff0c;方法还挺多的&#xff0c;这里只是给出流程最简洁、最适合自己的方法&#xff0c;供自己日后回顾&#xff0c;如果大家想学习更多方法可以百度一下。 方法…

【PyQt5】一文向您详细介绍 self.setGeometry() 的作用

【PyQt5】一文向您详细介绍 self.setGeometry() 的作用 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;985高校的普通本…

ES中下载ik解决版本不一致问题

1.链接&#xff1a; https://github.com/infinilabs/analysis-ik/releases/tag/v7.17.7 2.我的ES版本是7.17.9 但是Ik没有7.19&#xff0c;只有7.17 3.下载之后创建ik&#xff0c;然后把下载的导入进去&#xff1a; 4.因为版本不一致 我们修改 把所有的7.17.7改为7.17.9然…

详解 HBase 的架构和基本原理

一、基本架构 StoreFile&#xff1a;保存实际数据的物理文件&#xff0c;StoreFile 以 HFile 的格式 (KV) 存储在 HDFS 上。每个 Store 会有一个或多个 StoreFile&#xff08;HFile&#xff09;&#xff0c;数据在每个 StoreFile 中都是有序的MemStore&#xff1a;写缓存&#…