AVL树:高度平衡的二叉搜索树

news2024/11/8 7:32:25

AVL树

  1. AVL树和BST树的联系
      答:BST树(二叉排序树)当节点的数据key有序时是一棵单支树,查找时效率直接降低到O(N)而不是树高,为了使树尽量两边均匀,设计出了AVL树,AVL树的左右高度差不超过1
  2. 为什么AVL树左右高度差不能追求完美至相等?
      答:不是因为不想,是做不到,因为插入节点很多情况下,可能左右两边差一个。此外,因为AVL树的平衡因子保持在0或1,使得AVL树形态上均匀平衡,避免了单支情况,搜索的时间复杂度保持O(logN)。

实现

1. 成员结构

  • 树节点:
      AVL树的每个节点是三叉链。插入一个节点,AVL树,需要查看平衡因子,而平衡因子需要向上更新。如果插入后,父节点的平衡因子变为0,则不需要继续向上更新,因为平衡因子0肯定不会对向上的节点造成影响。如果插入节点使得父节点平衡因子成1,则继续向上更新,发现有2的节点,就停止,开始考虑旋转了,因为局部已不平衡了,再更新也没意义。
    总结:
  1. AVL树的每个节点有平衡因子(bf),bf使得AVL树能保持平衡。所以需要成员变量:bf。
  2. 给某节点插入或删除当前节点会从局部从底向上影响整个或局部树的balance factor,会影响到影响父亲节点的bf,所以AVL树节点需要从当前向上对父亲节点的bf值更新,需要找父亲节点,所以要三个指针
  3. 每个节点需要存储KV对。
  4. 构造函数,每个节点需要初始化bf为0。
  5. 不用平衡因子也能实现AVL,但比较麻烦。

代码如下:

template<class K, class V>
struct AVLTreeNode
{
	//三叉链
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;

	//存储的键值对
	pair<K, V> _kv;

	//平衡因子(balance factor)
	int _bf; //右子树高度-左子树高度

	//构造函数
	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};
  • AVL树类
template<class K, class V>
class AVLTree
{
private:
	typedef AVLTreeNode<K, V> Node;
public:
	AVLTree()
		: _pRoot(nullptr)
	{}
	bool Insert(const pair<K, V>& kv);
	bool IsAVLTree();
private:	// 防止暴露根节点
	bool _IsAVLTree(Node* pRoot);
	size_t _Height(Node* pRoot);
	void RotateL(Node* parent);
	void RotateR(Node* parent);
	void RotateRL(Node* parent);
	void RotateLR(Node* parent);
	Node* _root;

实现AVL树的方法:

  • 插入insert(): 参数:KV键值对:const pair<K, V>& kv

步骤:

  1. 先判断当前树是否有根,如果无根,则把插入的节点作根。
  2. 与1对立,已有根,则现在插入节点非根,则按照BST(二叉搜索树)我们要寻找一个合适的叶子节点位置做插入。所以:

a. 设置parent = nullptr,cur = _root ,被插值大,就把cur给右边走,同时更新parent,被插值小,cur就给左边走。while()寻找直到停止。
b. 如果找到了相等,返回false,意思是没做插入

  1. 之前一步如果没返回值,说明来到了合适叶子节点位置,那么就判断cur在parent左还是右,然后直接插入该节点、父亲链接该节点。以上,还是BST的老操作,下面是AVL的核心:更新平衡因子balance factor(bf)的操作。

  2. 更新平衡因子,如果parent==nullptr,则说明我们插入的是根节点,则不用做,对于单节点来说,bf一定为0且在节点的构造函数中已经初始化设置为0了。所以主要的操作都放在while(parent != nullptr)中,也就是说,插入完非根节点后,我们需要循环地更新整个parent和cur的bf值,需要循环的原因是:给cur的孩子插入节点后,可能对cur和cur的parent的bf做了+1或-1,但是还需要向上更新,因为只有更新到parent->bf为0可以停止,或parent->bf==2,或旋转后停止。因为旋转调节后,parent一定为0,所以旋转完break。下面具体讨论,while(parent!=nullptr)中的写法。

  3. 刚刚对cur的孩子做了插入,判断cur是parent的左孩子还是右孩子,规定左孩子侧的插入,bf–,而右孩子侧的插入,bf++,给节点插入左孩子和右孩子也直接是bf–和bf++。若parent->bf == 0 ,则退出循环,因为不会再对上面的子树bf做影响了,该AVL树是平衡的。然而,在bf++或bf–之后可能造成parent的|bf|==2,平衡因子bf为2就需要旋转调整。下面讨论parent和cur不同bf值时需做的调整。

  4. parent->bf == 0时break,退出循环,因为不会影响。(上面5中其实说到过,这里展开讨论)

  5. abs(parent->bf == 1),则向上更新bf值。

cur = parent;
parent = parent->_parent;
  1. abs(parent->bf == 2),bf==2,需要旋转调整bf值使AVL树平衡。以下,根据cur、和parent的bf值4种不同情况,有以下4种:9、10、11、12不同情况的旋转策略。

说明

4种不同旋转的情景,都在插入节点后,向上更新平衡因子出现的某节点(parent)的bf(平衡因子) 为+2或-2时做调整。

cur为±1且parent为±2理由:

情景:找到合适节点的孩子位置插入节点,该孩子节点cur本身为空,cur->bf为0,cur的父亲parent->bf可能有孩子,也可能没有孩子,最多parent为+1或-1,bf小点可能为0,如果parent->bf不为0,遵循下面逻辑向上调整:
cur变为parent,cur的bf为+1或-1,直到parent为+2或-2时,要旋转,所以每次需要旋转,都是parent为+2或-2,cur为+1或-1,不会是0

cur = parent;
parent = parent->_parent;

此外,旋转无需在意外部cur是谁
9. 左旋: 参数:parent;效果:parent到右边下去了,cur从右边向左上来了;
parent->bf = 2,cur->bf = -1,给右孩子的右侧插导致。
情境图如下:
给C插入,更新cur和parent的bf值,cur为subR时,发现parent的bf为2,左旋后,让subR做parent,parent下去。
请添加图片描述
按如上情境图,可知parent、subR(cur)位置变化了,且subRL给了parent。涉及3个节点。
步骤:

  1. 定位subR、subRL、parentParent(祖父,爹之爹)这些关系要发生改变的节点。
  2. 改parent和subR的关系。
  3. parent和subRL的关系。subRL给了parent。考虑subRL不存在的情况,比如单支时。
  4. 祖父的儿子更新为subR,考虑祖父为空时(即parent为根节点),不需要改变祖父的孩子指向。祖父不为空,则判断该链到左还是右。
void RotateL(Node* parent)
	{
		// 1. 定位要发生变化的节点
		Node* subR = parent->_right;
		Node* subRL = parent->_right;
		Node* parentParent = parent->_parent;

		// 2. parent和subR的关系改变:情境图靠左两节点的关系
		parent->_parent = subR;	// 原始par要下去
		subR->_left = parent;

		// 3. parent和subRL的关系:
		parent->_right = subRL;
		if (subRL)	// subRL可能不存在:特殊如整体是单支的情况下
		{
			subRL->_parent = parent;
		}

		// 4. 让原始parent->par,即原始祖父的儿子变subR
		if (parentParent == nullptr)	// parent是根时 以祖父可不存在
		{
			_root = subR;
			subR->_parent = nullptr;	// subR的父亲不再是parent
		}
		else  // 祖父不空	则判断subR作为新儿,在左边还是右
		{
			if (parent == parentParent->_left)
				parentParent->_left = subR;
			else
				parentParent->_right = subR;
			subR->_parent = parentParent;
		}
		// 更新bf:旋转前,原始subR、parent一个abs(1)、一个abs(2),两个bf都该置0
		subR->_bf = parent->_bf = 0;
	}
  1. 右旋:参数:parent;效果:parent下去,cur上来;
    parent->bf = -2,cur->bf = -1,给左孩子的左侧插导致

请添加图片描述
按如上情境图可知:parent、subL(cur)位置变化了,且subLR给了parent。涉及3个节点。
步骤:

  1. 定位要发生变化的点
  2. subL和par关系变化
  3. 原来的subLR给了par,考虑subLR不存在,即单支的情况
  4. 建立pP和subL的关系,考虑parent是根时 pP不存在的情况
  5. 更新bf平衡因子:subR(cur)、和parent旋转完 bf 都为0了
void RotateR(Node* parent)
	{
		// 1。定位要发生变化的点
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* parentParent = parent->_parent;

		// 2。subL和par关系变化
		subL->_right = parent;
		parent->_parent = subL;

		// 3。原来的subLR给了par,考虑subLR不存在,即单支的情况
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		// 4。建立pP和subL的关系,考虑parent是根时 pP不存在的情况
		if (parentParent == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else  // par不是根,且pp存在时
		{
			if (parent == parentParent->_left)
			{
				parentParent->_left = subL;
			}
			else  // par原始在pp右侧
			{
				parentParent->_right = subL;
			}
			subL->_parent = parentParent;
		}
		// 4. 更新平衡因子:subR(cur)、和parent旋转完 bf 都为0了
		subL->_bf = parent->_bf = 0;
	}

  1. 左右双旋:参数:parent。(不管怎么旋,都是发现当前节点的bf绝对值为2了)

有三种情况:

  • 情况1
    请添加图片描述
  • 情况2:
    请添加图片描述
  • 情况3:
    请添加图片描述

  如上三种情境图都需要左右双旋。当给左子树的右侧的左b插或c插,都会导致parent的bf==2。此外,abcd为空,subLR为新插节点,也会导致parent的bf==2。所以有三种情境下需要左右双旋
如何区分三种左右双旋的情况呢?
以subLR的bf值做区分。

  1. 给b插,subLR的bf为-1。
  2. 给c插,subLR的bf为1。
  3. a、b、c、d为空,subLR的bf为0。此时是因为subLR为新插节点,插入导致的不平衡

给subLR做插入导致的不平衡,拿subLR->bf做bf变量区分。

  • 情况1:subLR->bf == -1
    请添加图片描述

旋转完:
subLR->bf = 0(bf变量 )
subL->bf = 0
parent->bf = 1

  • 情况2:subLR->bf == 1
    请添加图片描述
    旋转完:
    subLR->bf = 0(bf变量)
    subL->bf = -1
    parent->bf = 0

  • 情况3:subLR->bf == 0

请添加图片描述
旋转完:
subLR->bf = 0(bf变量)
subL->bf = 0
parent->bf = 0

步骤:(以上三者步骤其实一样

  1. 确定几个被改变的节点,parent、subL、subLR
  2. 以subL为parent左旋(直接调用函数),使得subLR替换subL位置。
  3. 对parent做右旋,使得subLR去parent位置,而parent向右下走。
  4. 对不同bf做判断,更新每种情况的三个点bf,每种情况的区别在旋转完后bf值不一样
// 左右双旋
	void RotateLR(Node* parent)
	{
		// 拿subL、subLR 该不平衡是因为在subLR的左或右插入造成的
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;	// 给它的儿子节点插入,即增加它孙子的时候  bf变了

		RotateL(subL);	//  以subL左旋 (利用旋转改变位置,虽然当前节点bf不是2)
		RotateR(parent);	//  以父亲右旋 
		
		// 更新bf
		if (bf == 1)
		{
			parent->_bf = 0;	// 原来的父亲会成为右儿子,bf 为0
			subL->_bf = -1;
			subLR->_bf = 0;	// 不管哪种情况,这个节点bf都会为0
		}
		else if (bf == -1)	// 给LR的左插入造成的
		{
			parent->_bf = 1;	// 原来的父亲会成为右儿子,bf 为0
			subL->_bf = 0;
			subLR->_bf = 0;	// 不管哪种情况,这个节点bf都会为0
		}
		else if (bf == 0)
		{
			parent->_bf = 0;	// 原来的父亲会成为右儿子,bf 为0
			subL->_bf = 0;
			subLR->_bf = 0;	// 不管哪种情况,这个节点bf都会为0
		}
		else  // 到这里后 一定错了
		{
			assert(false);
		}

	}

  1. 右左双旋:参数:parent。(不管怎么旋,都是发现当前节点的bf绝对值为2了)

三种情况均是因为在subRL的孩子上插入导致的不平衡,以subRL的bf值为变量bf区分以下三种情况。

  • 情况一:
    bf(subRL->bf) == 1
    请添加图片描述

  • 情况二:
    bf(subRL->bf) == -1
    请添加图片描述

  • 情况三:
    bf(subRL->bf) == 0
    请添加图片描述

步骤:(以上三者步骤一样

  1. 确定几个被改变的节点,parent、subR、subRL
  2. 以subR为parent右旋(直接调用函数),使得subRL替换subR位置
  3. 对parent做左旋,使得subRL去parent位置,而parent向右下走
  4. 对不同bf做判断,因为每种情况的区别在旋转完bf值不一样
// 右左双旋
	void RotateRL(Node* parent)
	{
		// 定位改变的节点
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;	// bf是被插入节点的父亲

		// 先右旋subR,因为subR要上去 再左旋parent,parent要下去(对照情境图)
		RotateR(subR);
		RotateL(parent);

		// 更新bf
		if (bf == 1)	// 情景一:
		{
			subRL->_bf = 0;
			parent->_bf = -1;
			subR->_bf = 0;
		}
		else if (bf == -1)	// 情景二:
		{
			subRL->_bf = 0;
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 0)	// 情景三:
		{
			subRL->_bf = 0;
			parent->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false); //在旋转前树的平衡因子就有问题
		}
	}

AVL树的验证:

  • 判断AVL树:AVL树是平衡的BST树
  1. 先根据中序遍历是否为升序 :

  2. 再判断每个节点平衡因子,二叉树的每个节点的左右子树的高度差的绝对值不超过 11,则二叉树是平衡二叉树。其实leetcode题中只符合2即可:剑指 Offer 55 - II. 平衡二叉树
    以求树节点深度height()函数为基础。

完美代码:

int height(TreeNode* root) {
        if (root == NULL) {
            return 0;
        } else {
            return max(height(root->left), height(root->right)) + 1;
        }
    }

    bool isBalanced(TreeNode* root) {
        if (root == NULL) {
            return true;
        } else {
            return abs(height(root->left) - height(root->right)) <= 1 && isBalanced(root->left) && isBalanced(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 lH = Height(root->_left);
	int rH = Height(root->_right);
	return abs(lH - rh) < 2 && _Isbalance(root->left) && _Isbalance(root->right);
}

完整代码

曾经在左旋,找节点,判断写错了。

#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;

	//平衡因子(balance factor)
	int _bf; //右子树高度-左子树高度

	//构造函数
	AVLTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _kv(kv)
		, _bf(0)
	{}
};

// AVL: 二叉搜索树 + 平衡因子的限制
template<class K, class V>
class AVLTree
{
private:
	typedef AVLTreeNode<K, V> Node;
public:
	AVLTree()
		: _root(nullptr)
	{}

	// AVL插 pair KV	每个节点中存的是KV,所以插入也根据KV做插入节点
	/*
	1. 先按BST走 cur走到空 :当前树空,则直接建根节点
	2. 按BST,parent = nullptr,cur = _root,让cur到合适的空位,相等就不插
	3. 判断该位置在父左还是右,其实已经走到了合适位置,直接根据parent和cur的值也能判断出来,链接到正确的L或R位置
	4. 更新平衡因子:
		看的是parent->bf,因为当前刚刚插入,bf == 0
		1. 新增在右 parent->bf++
		2. 新增在左	parent->bf--
		3. 更新后,如果parent->bf == 1 or -1,需要继续往上更新,因为 -1表示左边高,1表示右边高,说明更新前,bf == 0,
		4. 更新后,如果parent->bf == 0,不用更新,说明现在平衡了,填上了矮的那边
		5. 更新后,parent->bf == 2 or -2 说明之前是1或-1 ,parent所在子树需要调整。
		6. 更新后,parent->bf:2 ,不可能,存在说明之前操作就有错
		向上更新到节点bf == 0 或 为2、-2、或 到根


	*/
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)	// AVL为空时,插入当前做根
		{
			_root = new Node(kv);
			return true;			// 插根则结束
		}
		Node* parent = nullptr;	// 插入非根,则找合适位置做cur,插入节点
		Node* cur = _root;
		while (cur)	// cur到合适位置,且期间要更新parent
		{
			if (kv.first < cur->_kv.first) //待插入结点的key值小于当前结点的key值
			{
				//往该结点的左子树走
				parent = cur;
				cur = cur->_left;
			}
			else if (kv.first > cur->_kv.first)	// 待插入节点key大于当前
			{
				//往该结点的右子树走
				parent = cur;
				cur = cur->_right;
			}
			else	// 相等已存在不做插入
			{
				return false;
			}
		}
		// 2. cur不存在,且cunr在合适位置,做插入
		cur = new Node(kv);	// cur的位置找到,创建节点,准备链接到parent的L或R
		if (kv.first < parent->_kv.first)	// 判断链接到L还是R
		{
			//插入到parent的左边
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			//插入到parent的右边
			parent->_right = cur;
			cur->_parent = parent;
		}

		// 3. 更新平衡因子BF:只要cur一直为+1或-1,且parent存在则一直向上更新,但发现parent->bf==2,会做旋转,使bf为0后break退出
		while (cur!=_root)	// parent???
		{
			if (cur == parent->_right)	// 查看在父亲左还是右插入
			{
				parent->_bf++;
			}
			else
			{
				parent->_bf--;
			}

			// 判断是否可以退出或者需要旋转 
			if (parent->_bf == 0)	// 平衡因子0 ,停止更新
				break;
			else if (abs(parent->_bf) == 1)	// 如果父亲bf为1,需要继续向上更新
			{
				parent = parent->_parent;
				cur = cur->_parent;
			}
			else if (abs(parent->_bf) == 2)	// parent->bf==2 需旋转,且转后 bf == 0 不用更新  
			{
				// parent2 cur1要左旋	
				if (parent->_bf == 2 && cur->_bf == 1)
					RotateL(parent);

				// -2 -1:右旋
				else if (parent->_bf == -2 && cur->_bf == -1)
					RotateR(parent);

				// par == -2  cur == -1,如画图中 本身左边多了一个,而左儿子的右孩子又多1
				else if (parent->_bf == -2 && cur->_bf == 1)
					RotateLR(parent);

				else if (parent->_bf == 2 && cur->_bf == -1)
					RotateRL(parent);

				break;	// 旋完,bf==0	可以退出
			}
			else  // 其它情况,需要 报错 理论上不会走到这里
			{
				assert(false);
			}
		}
		// 沿着当前往上更新平衡因子
		return true;	
	}

	// AVL树的验证
	bool IsAVLTree()
	{
		return _IsAVLTree(_root);
	}

	void InOrder()
	{
		_InOrder(_root);
	}

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

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


	// 根据AVL树的概念验证pRoot是否为有效的AVL树
	bool _IsAVLTree(Node* pRoot)
	{
		
		if (pRoot == nullptr)
			return true;
		int lh = _Height(pRoot->_left);
		int rh = _Height(pRoot->_right);
		return abs(lh - rh) < 2 && _IsAVLTree(pRoot->_left) && _IsAVLTree(pRoot->_right);
	}

	size_t _Height(Node* pRoot)
	{
		if (pRoot == nullptr)
			return 0;
		int lh = _Height(pRoot->_left);
		int rh = _Height(pRoot->_right);
		return max(lh, rh)+1;
	}

	// dragon的代码
	// 左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* parentParent = parent->_parent;

		//1、建立subR和parent之间的关系
		parent->_parent = subR;
		subR->_left = parent;

		//2、建立parent和subRL之间的关系
		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		//3、建立parentParent和subR之间的关系
		if (parentParent == nullptr)
		{
			_root = subR;
			subR->_parent = nullptr; //subR的_parent指向需改变
		}
		else
		{
			if (parent == parentParent->_left)
			{
				parentParent->_left = subR;
			}
			else //parent == parentParent->_right
			{
				parentParent->_right = subR;
			}
			subR->_parent = parentParent;
		}

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


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

		//1、建立subL和parent之间的关系
		subL->_right = parent;
		parent->_parent = subL;

		//2、建立parent和subLR之间的关系
		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		//3、建立parentParent和subL之间的关系
		if (parentParent == nullptr)
		{
			_root = subL;
			_root->_parent = nullptr;
		}
		else
		{
			if (parent == parentParent->_left)
			{
				parentParent->_left = subL;
			}
			else //parent == parentParent->_right
			{
				parentParent->_right = subL;
			}
			subL->_parent = parentParent;
		}

		//4、更新平衡因子
		subL->_bf = parent->_bf = 0;
	}


	// LR
	//左右双旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf; //subLR不可能为nullptr,因为subL的平衡因子是1

		//1、以subL为旋转点进行左单旋
		RotateL(subL);

		//2、以parent为旋转点进行右单旋
		RotateR(parent);

		//3、更新平衡因子
		if (bf == 1)
		{
			subLR->_bf = 0;
			subL->_bf = -1;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			subLR->_bf = 0;
			subL->_bf = 0;
			parent->_bf = 1;
		}
		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;

		//1、以subR为轴进行右单旋
		RotateR(subR);

		//2、以parent为轴进行左单旋
		RotateL(parent);

		//3、更新平衡因子
		if (bf == 1)
		{
			subRL->_bf = 0;
			parent->_bf = -1;
			subR->_bf = 0;
		}
		else if (bf == -1)
		{
			subRL->_bf = 0;
			parent->_bf = 0;
			subR->_bf = 1;
		}
		else if (bf == 0)
		{
			subRL->_bf = 0;
			parent->_bf = 0;
			subR->_bf = 0;
		}
		else
		{
			assert(false); //在旋转前树的平衡因子就有问题
		}
	}

	// 左单旋:给右孩子的右边插入导致  需要左旋
	/*	
	只要是parent 2 cur1 就是单纯左旋
		此外,高度如果100w,更新20次平衡因子,其实做的很少,效率还是很高,
		更新不会太耗时,且只会旋转一次,所以旋转也不太耗时,对CPU来说,不算什么
		所以一次插入只会有一次更新,而保持平衡使得树的查找,效率更高,虽然不控制bf,插入简单,但是树的查找效率不行
		AVL树的优势在查找
			// 删除:也要旋转,因为删除可能影响bf
	只有parent和subR平衡因子,收到影响,
	*/ 



	 左单旋
	//void RotateL(Node* parent)
	//{
	//	// 1. 定位要发生变化的节点
	//	Node* subR = parent->_right;
	//	Node* subRL = subR->_left;
	//	Node* parentParent = parent->_parent;

	//	// 2. parent和subR的关系改变:情境图靠左两节点的关系
	//	parent->_parent = subR;	// 原始par要下去
	//	subR->_left = parent;

	//	// 3. parent和subRL的关系:
	//	parent->_right = subRL;
	//	if (subRL)	// subRL可能不存在:特殊如整体是单支的情况下
	//	{
	//		subRL->_parent = parent;
	//	}

	//	// 4. 让原始parent->par,即原始祖父的儿子变subR
	//	if (parentParent == nullptr)	// parent是根时 以祖父可不存在
	//	{
	//		_root = subR;
	//		subR->_parent = nullptr;	// subR的父亲不再是parent
	//	}
	//	else  // 祖父不空	则判断subR作为新儿,在左边还是右
	//	{
	//		if (parent == parentParent->_left)
	//			parentParent->_left = subR;
	//		else
	//			parentParent->_right = subR;
	//		subR->_parent = parentParent;
	//	}
	//	// 更新bf:旋转前,原始subR、parent一个abs(1)、一个abs(2),两个bf都该置0
	//	subR->_bf = parent->_bf = 0;
	//}

	 右单旋
	//void RotateR(Node* parent)
	//{
	//	cout << "右旋了" << endl;
	//	// 1。定位要发生变化的点
	//	Node* subL = parent->_left;
	//	Node* subLR = subL->_right;
	//	Node* parentParent = parent->_parent;

	//	// 2。subL和par关系变化
	//	subL->_right = parent;
	//	parent->_parent = subL;

	//	// 3。原来的subLR给了par,考虑subLR不存在,即单支的情况
	//	parent->_left = subLR;
	//	if (subLR)
	//		subLR->_parent = parent;

	//	// 4。建立pP和subL的关系,考虑parent是根时 pP不存在的情况
	//	if (parentParent == nullptr)
	//	{
	//		_root = subL;
	//		_root->_parent = nullptr;
	//	}
	//	else  // par不是根,且pp存在时
	//	{
	//		if (parent == parentParent->_left)
	//		{
	//			parentParent->_left = subL;
	//		}
	//		else  // par原始在pp右侧
	//		{
	//			parentParent->_right = subL;
	//		}
	//		subL->_parent = parentParent;
	//	}
	//	// 4. 更新平衡因子:subR(cur)、和parent旋转完 bf 都为0了
	//	subL->_bf = parent->_bf = 0;
	//}


	 右左双旋
	//void RotateRL(Node* parent)
	//{
	//	cout << "右左旋" << endl;
	//	// 定位改变的节点
	//	Node* subR = parent->_right;
	//	Node* subRL = subR->_left;
	//	int bf = subRL->_bf;	// bf是被插入节点的父亲

	//	// 先右旋subR,因为subR要上去 再左旋parent,parent要下去(对照情境图)
	//	RotateR(subR);
	//	RotateL(parent);

	//	// 更新bf
	//	if (bf == 1)	// 情景一:
	//	{
	//		subRL->_bf = 0;
	//		parent->_bf = -1;
	//		subR->_bf = 0;
	//	}
	//	else if (bf == -1)	// 情景二:
	//	{
	//		subRL->_bf = 0;
	//		parent->_bf = 0;
	//		subR->_bf = 1;
	//	}
	//	else if (bf == 0)	// 情景三:
	//	{
	//		subRL->_bf = 0;
	//		parent->_bf = 0;
	//		subR->_bf = 0;
	//	}
	//	else
	//	{
	//		assert(false); //在旋转前树的平衡因子就有问题
	//	}

	//}

	 左右双旋
	//void RotateLR(Node* parent)
	//{
	//	cout << "左右旋转" << endl;
	//	// 拿subL、subLR 该不平衡是因为在subLR的左或右插入造成的
	//	Node* subL = parent->_left;
	//	Node* subLR = subL->_right;
	//	int bf = subLR->_bf;	// 给它的儿子节点插入,即增加它孙子的时候  bf变了

	//	RotateL(subL);	//  以subL左旋 (利用旋转改变位置,虽然当前节点bf不是2)
	//	RotateR(parent);	//  以父亲右旋 
	//	
	//	// 更新bf
	//	if (bf == 1)
	//	{
	//		parent->_bf = 0;	// 原来的父亲会成为右儿子,bf 为0
	//		subL->_bf = -1;
	//		subLR->_bf = 0;	// 不管哪种情况,这个节点bf都会为0
	//	}
	//	else if (bf == -1)	// 给LR的左插入造成的
	//	{
	//		parent->_bf = 1;	// 原来的父亲会成为右儿子,bf 为0
	//		subL->_bf = 0;
	//		subLR->_bf = 0;	// 不管哪种情况,这个节点bf都会为0
	//	}
	//	else if (bf == 0)
	//	{
	//		parent->_bf = 0;	// 原来的父亲会成为右儿子,bf 为0
	//		subL->_bf = 0;
	//		subLR->_bf = 0;	// 不管哪种情况,这个节点bf都会为0
	//	}
	//	else  // 到这里后 一定错了
	//	{
	//		assert(false);
	//	}
	//}

private:
	Node* _root;
};

请添加图片描述

分析旋转对AVL树的性能影响+AVL性能分析

AVL树的查找效率平均为:O(logN),也就是树高。

  • 但它的过程中的旋转是否会影响树的效率?
      答:如果节点为100W,插入一个节点,最多旋转一次,而更新最多更新20次平衡因子,而这样的做法使得查找时,没有单支树的情况,使得比BST树查找的效率提高了很多。

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

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

相关文章

sql语句练习题1

1、选择部门30中的所有员工&#xff1b; 要注意到查的是所有员工 代码如下&#xff1a; mysql> select * from emp where deptno 30;2、列出所有办事员(CLERK)的姓名&#xff0c;编号和部门编号&#xff1b; 注意的是要查的是姓名&#xff0c;编号和部门编号 范围限定的是…

并发编程的原子性 != 事务ACID的原子性

△Hollis, 一个对Coding有着独特追求的人△这是Hollis的第 412 篇原创分享作者 l Hollis来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09;关于原子性&#xff0c;很多人在多个地方都听说过&#xff0c;大家也都背的很熟悉。在事务的ACID中&#xff0c;有原子性…

儒家思想和道家思想的三个主要差异

孔子、孟子、老子、庄子&#xff0c;这四位古代思想家被称为“中国四哲”&#xff0c;他们分别代表了儒家和道家思想。这两大思想流派&#xff0c;是数千年来中国人智慧的结晶和文化的瑰宝。01先秦儒家思想的发展&#xff0c;经过了三个阶段&#xff0c;第一阶段是孔子&#xf…

CHAPTER 7 Ansible playbook(四)

ansible-playbook7.1 roles&#xff08;角色&#xff09;7.1.1 Ansible Roles 介绍7.1.2 Roles结构7.1.3 存储和查找角色7.1.4 制作一个Role7.1.5 使用角色7.1.5.1 经典方法7.1.5.2 import_role7.1.6 如何使用Galaxy7.1 roles&#xff08;角色&#xff09; 7.1.1 Ansible Role…

windows docker安装prometheus和grafana

文章目录docker安装prometheusdocker安装grafanawindows安装windows_exporterprometheus配置新增windows_exporter的job,配置grafana导入windows模板即可出现酷炫大屏出现酷炫画面完成docker安装prometheus 拉取镜像,在D盘下创建prometheus.yml配置文件,映射到docker里面d:/se…

【pandas】教程:8-如何组合多个表格的数据

Pandas 组合多个表格的数据 本节使用的数据为 data/air_quality_no2_long.csv&#xff0c;链接为 pandas案例和教程所使用的数据-机器学习文档类资源-CSDN文库 导入数据 NO2NO_2NO2​ import pandas as pd air_quality_no2 pd.read_csv("data/air_quality_no2_long.cs…

二、python编程进阶02:模块和包

目录 1. python中的模块是什么 2. 导入模块: 学习import语句 2.1 import语句介绍 2.2 import导入模块的语法 2.3 导入自己的模块 2.4 导入数字开头或者带空格的模块 3. 编写自定义模块 3.1 给自定义模块编写测试代码 3.2 给自定义模块模块编写说明文档 4. 模块的搜索…

1215. 小朋友排队(树状数组应用 -- 逆序对个数)

题目如下&#xff1a; 思路 or 题解 我们可以得出交换的次数 > 逆序对个数 kkk 我们可以发现 所有 位置 左边大于它的个数 右边小于它的个数和 kik_iki​ 等于 k∗2k*2k∗2 我们可以简单证明出(感觉出)&#xff1a;答案就是 ∑1n(1ki)∗ki2\sum^n_1 \frac{(1 k_i) * k_i}…

JavaScript 错误

文章目录JavaScript 错误 - throw、try 和 catchJavaScript 错误JavaScript 抛出&#xff08;throw&#xff09;错误JavaScript try 和 catchThrow 语句实例实例JavaScript 错误 - throw、try 和 catch try 语句测试代码块的错误。 catch 语句处理错误。 throw 语句创建自定义错…

string的使用介绍

目录 标准库中的string类 string类(了解) 编码介绍 string类的常用接口说明 Member functions 测试一&#xff1a;创建对象 测试二&#xff1a;遍历字符串 Iterators 测试三&#xff1a;反向迭代器(Iterators) Capacity 测试四&#xff1a;容器相关(Capacity) 测试…

Redis企业云如何通过缓存轻松扩展到亿级请求?

你是否在春运抢票过程中遇到12306 APP瘫痪&#xff1f; 你是否在双十一抢好物的时候显示系统繁忙&#xff1f; 你是否在微博刷某个爆了的娱乐新闻时显示页面走丢了&#xff1f; 前几天热搜上好像又说小红书又崩溃了&#xff1f; 当用户请求量过高&#xff0c;数据库无法支撑时&…

Annotation(注解)

一、注解概述1.从 JDK 5.0 开始,Java 增加了对元数据(MetaData) 的支持,也就是Annotation(注解)2.Annotation 其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用 Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些…

Go第 6 章:函数、包和错误处理

Go第 6 章&#xff1a;函数、包和错误处理 6.1 为什么需要函数 6.1.1请大家完成这样一个需求: 输入两个数,再输入一个运算符(,-,*,/)&#xff0c;得到结果.。 6.1.2使用传统的方法解决 分析一下上面代码问题 上面的写法是可以完成功能, 但是代码冗余同时不利于代码维护函数…

SAP FICO 成本对象控制解析

成本对象控制&#xff08;Cost Object Cotrol&#xff09;是指对不同的成本对象&#xff0c;比如成本收集器、生产订单、销售订单等进行成本的期末结算。基于这些不同的成本对象&#xff0c;SAP在成本对象控制菜单下面细分了相应的操作&#xff08;SAP 菜单 →会计核算 →控制 …

JavaScript篇.day10-面向对象,对象,构造函数,this关键字,原型

目录面向对象对象构造函数this关键字原型面向对象面向过程: 在开发过程中,关注过程的开发方式. 在开发时关注每一个细节,步骤和顺序.面向对象: 在开发过程中,只需要找一个对象来完成事情的开发思想对象: 在生活中,万物皆对象 封装: 将完成步骤封装在对象内部属性: 对象的特征核…

Java泛型上界与泛型方法的应用 | 如何通过泛型类获取任意类型的三个数的最大值?

目录 一、引言 二、泛型上界 1、什么是泛型的上界 2、泛型上界的语法 三、泛型方法 1、泛型方法的语法 2、泛型方法的类型推导 三、编程分析 1、MyCompare泛型类 2、泛型方法实现 四、总结 一、引言 初学Java时&#xff0c;同学们基本都会遇到这样一个基础编程题&am…

平面设计师去哪里找素材?

5个平面设计素材网站&#xff0c;赶紧收藏&#xff01; 1、菜鸟图库 https://www.sucai999.com/?vNTYwNDUx ​ 站内平面海报、UI设计、电商淘宝、免抠、高清图片、样机模板等素材非常齐全。还有在线抠图、CDR版本转换功能&#xff0c;能有效的为设计师节省找素材时间&#x…

MySQL查询训练题1

表信息&#xff1a; dept表和emp表 bonus表和salgrade表 练习题&#xff1a; 1、选择部门30中的所有员工&#xff1b; select * from Emp where DEPTNO30;2、列出所有办事员(CLERK)的姓名&#xff0c;编号和部门编号&#xff1b; select ENAME 姓名,EMPNO 编号,DEPTNO 部门…

【vue2中使用axios和插槽】一.组件的生命周期;二.vue2中使用axios;三.插槽

目录 一.组件的生命周期 1.组件的生命周期经历的阶段&#xff1a; &#xff08;1&#xff09;创建阶段&#xff1a;beforeCreate、created、beforeMount、mounted &#xff08;2&#xff09;运行阶段&#xff1a;beforeUpdate、update &#xff08;3&#xff09;销毁阶段&a…

C++ 学习 Day.10(标准模板库简介)

标准模板库&#xff1a; 简单地说&#xff0c;标准模板库&#xff08;STL&#xff09;是一组模板类和函数&#xff0c;向程序员提供了&#xff1a; • 用于存储信息的容器&#xff1b; • 用于访问容器存储的信息的迭代器&#xff1b; • 用于操作容器内容的算法。 关于STL可见…