C++:高度平衡二叉搜索树(AVLTree) [数据结构]

news2025/2/22 22:19:31

目录

一、AVL树

二、AVL树的理解 

1.AVL树节点的定义

2.AVL树的插入

 2.1更新平衡因子

3.AVL树的旋转

三、AVL的检查

四、完整代码实现


一、AVL树

AVL树是什么?我们对 map / multimap / set / multiset 进行了简单的介绍,可以发现,这几个容器有个共同点是:其底层都是按照二叉搜索树来实现的。但是二叉搜索树有其自身的缺陷,假如往树中插入的元素有序或者接近有序,二叉搜索树就会退化成单支树,时间复杂度会退化成 O(N),因此 map、set 等关联式容器的底层结构是对二叉树进行了平衡处理,即采用平衡树来实现。

此二叉树插入元素有序,进行查找的时候效率会相当低下,甚至当接近为单支树的时候,查找效率会相当于在顺序表中的查找效率。因此俄罗斯的数学家G.M.Adelson-VelskiiE.M.Landis发明了一个解决方案:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。这也就是AVL树的由来。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树: 

1、它的左右子树都是AVL树

2、左右子树高度之差的绝对值不会超过1

左右子树的高度差也叫做平衡因子,在这样的树中进行搜索,时间复杂度是O(logN)。

二、AVL树的理解 

1.AVL树节点的定义

AVL 树节点是一个 三叉链结构,除了指向左右孩子的指针,还有一个指向其父亲的指针,数据域是键值对,即 pair 对象,还引入了平衡因子,用来判断是否需要进行平衡操作。 

// AVL树节点的定义(KV模型)
template<class K, class V>
struct AVLTreeNode
{
    AVLTreeNode<T>* _left;   // 该节点的左孩子
    AVLTreeNode<T>* _right;  // 该节点的右孩子
    AVLTreeNode<T>* _parent; // 该节点的双亲指针
 
    pair<K, V> _kv;          // 键值对
    int _bf;                 // 该节点的平衡因子(balance factor) = 右子树高度-左子树高度
 
    // 构造函数
    AVLTreeNode(const pari<K, V>& kv)
        : _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _kv(kv)
        , _bf(0)
    {}
};
 
// AVL树的定义(KV模型)
template<class K, class V>
class AVLTree
{
	typedef AVLTreeNode<K, V> Node;
public:
	// 成员函数
 
private:
	Node* _root;
}

2.AVL树的插入

AVL 树就是在二叉搜索树的基础上引入了平衡因子,因此 AVL 树也可以看成是二叉搜索树。那么 AVL 树的插入过程可以分为两步:

  1. 按照二叉搜索树的方式插入新节点到 AVL 树中。
  2. 新节点插入后,AVL 树的平衡性可能会遭到破坏,此时就需要更新平衡因子,并检测是否破坏了 AVL 树的平衡(控制树的平衡(旋转操作))。
// 插入节点
bool Insert(const pair<K, V>& kv)
{
    // 如果树为空,则直接插入节点
    if (_root == nullptr)
    {
		_root = new Node(kv);
		return true;
	}
 
	// 如果树不为空,找到适合插入节点的空位置
    Node* parent = nullptr; // 记录当前节点的父亲
    Node* cur = _root;      // 记录当前节点
 
    while (cur) // while循环结束,说明找到适合插入节点的空位置了
    {
        if(kv.first > cur->_kv.first) // 插入节点键值k大于当前节点
        {
            parent = cur;
            cur = cur->_right;
        }
        else if(kv.first < cur->_kv.first) // 插入节点键值k小于当前节点
        { 
            parent = cur;
            cur = cur->_left;
        }
        else // 插入节点键值k等于当前节点
        {
            return false;
        }
    }
    
    // 插入新节点
    cur = new Node(kv); // 申请新节点
    // 判断当前节点是父亲的左孩子还是右孩子
    if (cur->_kv.first > parent->_kv.first)
    {
        parent->_right = cur;     
 
    }
    else
    {
        parent->_left = cur;
    }
    cur->_parent = parent;
 
    // 控制平衡
	// 1、更新平衡因子
    
    // ...
 
    return true;
}

 2.1更新平衡因子

(1) 插入新节点cur 插入后,AVL树的平衡性可能会遭到破坏,引入平衡因子的意义也就在于此,parent 的平衡因子一定需要调整,在插入之前,parent 的平衡因子分为三种情况:-1,0,1,分以下两种情况:

①如果插入到 新节点父亲(parent)的左侧,只需给父亲(parent)的平衡因子--(_bf--)即可。

②如果插入到 新节点父亲(parent)的右侧,只需给父亲(parent)的平衡因子++(_bf++)即可。

(2) 新节点父亲的平衡因子更新以后,又会分为 3 种情况: 

①如果更新以后,parent 的平衡因子是 0(则说明插入之前 parent 的平衡因子之前一定为 1/-1),说明父亲所在子树高度没变(因为把矮的那边给填补上了),此时满足 AVL 树的性质,插入成功,不需要继续往上更新。

②如果更新以后,parent 的平衡因子是 1/-1(则说明插入之前 parent 的平衡因子 一定为 0),说明父亲所在子树高度增加,需要继续往上更新。(最坏情况:往上一直更新到根节点)。

③如果更新以后,parent 的平衡因子是 2/-2,说明父亲所在子树出现了不平衡,需要对其进行旋转处理

根据此理解可以写出代码来更新平衡因子:

// 插入节点
bool Insert(const pair<K, V>& kv)
{
    // 控制平衡
	// 1、更新平衡因子
    
    while (parent) // 最坏情况:更新到根节点
    {
        // 更新双亲的平衡因子
        if (cur == parent->_left) // 新节点插入在父亲的左边
            parent->_bf--;
        else // 新节点插入在父亲的右边
            parent->_bf++;
 
        // 更新后检测双亲的平衡因子
        if (0 == pParent->_bf)
        {    
            break;
        }
 
        //else if (1 == parent->_bf || -1 == parent->_bf)
        else if (abs(parent->_bf) == 1) // 插入前双亲的平衡因子是0,插入后双亲的平衡因为为1 或者 -1 ,说明以双亲为根的二叉树的高度增加了一层,因此需要继续向上调整
        {
            cur = parent;
            parent = cur->_parent;
        }
 
        else if (abs(parent->_bf) == 2) // 双亲的平衡因子为正负2,违反了AVL树的平衡性,需要对以parent为根的树进行旋转处理
        {
            // 1、父节点的右边高,左边低,需要往左旋
            if (parent->_bf == 2 && cur->_bf == 1) 
		    {
				RotateL(parent); // 左单旋
			}
 
            // 2、父节点的左边高,右边低,需要往右旋
			else if ((parent->_bf == -2 && cur->_bf == -1))
			{
				RotateR(parent); // 右单旋
			}
            
            // 3、父节点的左边高,且父节点左孩子的右边高
			else if (parent->_bf == -2 && cur->_bf == 1) 
			{
				RotateLR(parent); // 左右双旋
			}
 
            // 4、父节点的右边高,且父节点右孩子的左边高
            else if (parent->_bf == 2 && cur->_bf == -1)
			{
				RotateRL(parent); // 右左双旋
			}
 
			break; // 旋转完成,树已平衡,退出循环
        }
 
        // 除了上述3种情况,平衡因子不可能有其它的值,报错处理
        else
        {
            assert(false);
        }
    }
    return true;
}

3.AVL树的旋转

那当上面的情况已经发生,AVL树应该如何进行平衡?这就引入了旋转的概念,旋转是在遵循二叉搜索树的规则下,让左右均衡,降低整颗树的高度。

AVL树中,当破坏了AVL树的平衡后,总共有四种会引发的旋转:

1. 新节点插入较高左子树的左侧,引发右单旋

[操作] 

1、让 subL 的右子树 subLR 成为 parent 的左子树(因为 subLR 的右子树根节点值 > 30,< 60)。
2、让 parent 成为 subL 的右子树(因为 60 > 30)。
3、让 subL 变成这个子树的根。

这一步操作前需要先判断下:parent 是根节点,还是一个普通子树

  • 如果是根节点,旋转完成后,则更新 subL 为新的根节点。
  • 如果是普通子树(可能是某个节点的左子树,也可能是右子树,这里作一个判断),然后更新 subL 为这个子树的根节点。

4、根据树的结构,更新 parent 和 subL 的平衡因子为 0。

在旋转过程中,更新双亲指针的指向,有以下几种情况需要考虑:

  • 30 节点的右孩子可能存在,也可能不存在。(subL 的右子树 subLR 可能存在,也可能为空。当不为空时才更新 subL 右子树 subLR 的双亲指针指向)。
  • 60 可能是根节点,也可能是子树。(旋转完成后,subL 的双亲节点,可能是空,也可能是 parent 原先的父节点。所以在更新 subL 的双亲指针前需要判断下)。

依次调整 subLR、parent、subL 的位置和双亲指针的指向,下面为右单旋代码:

// 右单旋
void _RotateR(Node* parent)
{  
    Node* subL = parent->_left; // subL : parent的左孩子
	Node* subLR = subL->_right; // subLR : parent左孩子的右孩子
 
    // 旋转完成之后,让subL的右子树subLR成为parent的左子树
    parent->_left = subLR;
    // 如果subLR存在,更新subLR的双亲指针,指向parent
    if (subLR)
	{
		subLR->_parent = parent;
	}
    
    // 因为parent可能是棵子树,因此在更新其双亲前必须先保存parent的父节点
    Node* ppNode = parent->_parent;
    
    // 让parent成为subL的右子树
    subL->_right = parent;
    // 更新parent的双亲指针,指向subL
    parent->_parent = subL;
 
    // 如果parent是根节点,根新指向根节点的指针
    if (_root == parent)
	{
		_root = subL;            // 更新subL为新的根
		subL->_parent = nullptr; // 更新subL的双亲指针,指向空
	}
    // parent不是根节点,就是一个普通子树
    else
    {
        // 判断parent原先是左孩子还是右孩子
        if (ppNode->_left == parent)
		{
			ppNode->_left = subL; // parent原先的双亲节点接管subL,subL为这个子树的根
		}
		else
		{
			ppNode->_right = subL;
		}
        subL->_parent = ppNode; // 更新subL的双亲指针
    }
 
    // 根据调整后的结构更新部分节点的平衡因子
    parent->_bf = pSubL->_bf = 0;
}

2. 新节点插入在较高右子树的右侧,引发左单旋

[操作]

1、让 subR 的左子树 subRL 成为 parent 的右子树(因为 subRL 的左子树根节点值 > 30,< 60)。
2、让 parent 成为 subR 的左子树(因为 30 < 60)。
3、让 subR 变成这个子树的根。

这一步操作前需要先判断下:parent 是根节点,还是一个普通子树

  • 如果是根节点,旋转完成后,则更新 subR 为新的根节点。
  • 如果是普通子树(可能是某个节点的左子树,也可能是右子树,这里作一个判断),然后更新 subR 为这个子树的根节点。

4、根据树的结构,更新 parent 和 subR 的平衡因子为 0。

在旋转过程中,更新双亲指针的指向,有以下几种情况需要考虑:

  • subR 的左子树 subRL 可能存在,也可能为空。(当不为空时才更新 subR 左子树 subRL 的双亲指针指向)。
  • 旋转完成后,subR 的双亲节点,可能是空,也可能是 parent 原先的父节点。(所以更新 subR 的双亲指针前需要判断下)。

依次调整 subRL、parent、subR 的位置和双亲指针的指向,左单旋代码:

// 左单旋
void treeRotateLeft(Node* parent)
{
    Node* subR = parent->_right; // subR:父亲的右孩子
    Node* subRL = subR->_left; // subRL:父亲的右孩子的左孩子(大于父亲,小于subR)
 
    // 让subRL成为父亲的右子树
    parent->_right = subRL;
    // 如果subRL不为空
    if (subRL)
    {
        subRL->_parent = parent; // 更新subRL双亲指针,指向parent
    }
 
    // 因为parent可能是棵子树,因此在更新其双亲前必须先保存parent的父节点
    Node* ppNode = parent->_parent;
    
    // 让parent成为subR的左子树
    subR->_left = parent; 
    // 更新parent双亲指针的指向
    parent->_parent = subR;
 
    // 判断parent是不是根节点
    if (parent == _root)
    {
        _root = subR;            // subR为新的根
        subR->_parent = nullptr; // subR双亲指针指向空
    }
 
    // 不是根节点,就是一个普通子树
    else
    {
        // 判断parent原先是左孩子还是右孩子
        if (ppNode->_left == parent)
        {
            ppNode->_left = subR; // parent原先的双亲节点接管subR,subR为这个子树的根
        }
        else
        {
            ppNode->_right = subR;
        }
        subR->_parent = ppNode; // 更新subR的双亲指针
    }
 
    // 根据树的结构,更新parent和subR的平衡因子
    parent->_bf = subR->_bf = 0;
}

3. 新节点插入较高右子树的左侧,引发先右单旋再左单旋

将新的节点插入到了 parent 右孩子的左子树上,导致的不平衡的情况。这时我们需要的是先对 parent 的右孩子进行一次右旋,再对 parent 进行一次左旋。

左右双旋操作后,根据树的结构,更新平衡因子时,需要注意:

插入新节点的位置不同,经过右左双旋后,得到树的结构也会有所不同,平衡因子也会有所不同,有以下三种情况:

  • 新节点插入到了 parent 右孩子的左子树的左边。
  • 新节点插入到了 parent 右孩子的左子树的右边。
  • 新节点就是 parent 右孩子的左孩子。

这里可以观察到一个现象,根据这个现象就很好推出旋转后的平衡因子:

节点 60 的左右子树被分走了,左子树 b 最终成了节点 30 的右子树,右子树 c 最终成了节点 90 的左子树。

// 右左双旋
void treeRotateRL(Node* parent)
{
    Node* subR = parent->_right; // 记录parent的右孩子
    Node* subRL = subR->_left;   // 记录parent的右孩子的左孩子
 
    // 旋转之前,因为插入新节点的位置不同,subRL的平衡因子可能为-1/0/1
    int bf = subRL->_bf; // 记录subRL的平衡因子
 
    RotateR(parent->_right); // 先对parent的右孩子进行右单旋
    RotateL(parent);         // 再对parent进行左单选
 
    // 旋转完成之后,根据树的结构对其他节点的平衡因子进行调整
    subRL->_bf = 0;
    if (bf == -1)
    {
        parent->_bf = 0;
        subR->_bf = 1;
    }
    else if (bf == 1)
    {
        parent->_bf = -1;
        subR->_bf = 0;
    }
    else if(bf == 0)
    {
        parent->_bf = 0;
        subR->_bf = 0;
    }
    else
    {
        assert(false);
    }
}

4. 新节点插入较高左子树的右侧,引发先左单旋再右单旋

将新的节点插入到了 parent 左孩子的右子树上,导致的不平衡的情况。这时我们需要的是先对 parent 的右孩子进行一次左旋,再对 parent 进行一次右旋。

void _RotateLR(PNode pParent)
{
    Node* subL = parent->_left; // 记录parent的左孩子
    Node* subLR = subL->_right; // 记录parent的左孩子的右孩子
    
    // 旋转之前,因为插入新节点的位置不同,subLR的平衡因子可能是-1/0/1
    int bf = subLR->_bf; // 记录subLR的平衡因子
    
    // 先对parent的左孩子进行左单旋
    RotateL(parent->_left);
    // 再对parent进行右单旋
    RotateR(parent);
 
    // 旋转完成之后,根据情况对其他节点的平衡因子进行调整
    subLR->_bf = 0;
    if (bf == -1)
	{
		parent->_bf = 1;
		subL->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = 0;
		subL->_bf = -1;
	}	
	else if (bf == 0)
	{
		parent->_bf = 0;
		subL->_bf = 0;
	}
	else
	{
		assert(false);
	}
}

【总结】

假如以 parent 为根的子树不平衡,即 parent 的平衡因子为 2/-2,分以下情况考虑:
1、parent 的平衡因子为 2,说明 parent 的右子树高,设 parent 的右子树的根为 subR。

  • 当 subR 的平衡因子为 1 时,执行左单旋。
  • 当 subR 的平衡因子为 -1 时,执行右左双旋。

2、parent 的平衡因子为 -2,说明 parent 的左子树高,设 parent 的左子树的根为 subL。

  • 当 subL 的平衡因子为 -1 时,执行右单旋。
  • 当 subL 的平衡因子为 1 时,执行左右双旋。

旋转完成后,原 parent 为根的子树个高度降低,已经平衡,不需要再向上更新。

三、AVL的检查

依据上面的实现可以基本实现AVL树,那如何验证AVL树是否正确?其实验证也很简单,只需要看每个节点的平衡因子是否等于对应的右子树减左子树的值即可。 

// 计算当前树的高度
int Height(Node* root)
{
    // 当前树为空,则高度为0
    if (root == nullptr)
        return 0;
 
    // 当前树的高度 = 左右子树中高度最大的那个加1
    return max(Height(root->_left), Height(root->_right)) + 1;
}

// 保持树的封装 进行检查AVL树
bool IsBalance1()
{
	return _IsBalance(_root);
}

// 进行检查AVL树
bool _IsBalance1(Node* root)
{
    // 当前树为空,说明是平衡的
	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,说明当前树是平衡的,则继续往下判断其它子树
	return abs(diff) < 2
		&& _IsBalance(root->_left)
		&& _IsBalance(root->_right);
}

四、完整代码实现

#pragma once

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

	int _bf;//平衡因子

	pair<K, V> _kv;//key

	//构造函数
	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)
	{
		//1.先按二叉搜索树的规则插入
		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->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				paretn = cur;
				cut = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//2.更新平衡因子
		while (parent)//有可能要更新到根节点
		{
			if (cur == parent->_right)
			{
				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)
			{
				//parent 的子树出现不平衡了,需要旋转处理
			}
		}
		return true;
	}

	//左单旋
	void RotateL(Node* parent)
	{
		/*Node* subR = parent->_right;
		parent->_right = subR->_left;
		subR->_left = parent;*///没有处理每个节点的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;

		//1.原来的parent是这棵树的根,现在subR是树的根
		//2.parent不是整棵树的根,那么链接关系要变,subR就要顶替parent的位置
		if (_root == parent)
		{
			_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 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 (_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;
	}

	// 先右单旋再左单旋
	void RotateRL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		int bf = subRL->_bf;

		RotateR(subR);
		RotateL(parent);

		if (bf == 0)
		{
			// 自己就是新增的节点
			parent->_bf = subR->_bf = 0;
		}
		else if (bf == -1)
		{
			// 在左子树进行的插入
			parent->_bf = 0;
			subR->_bf = 1;
			subRL->_bf = 0;
		}
		else if (bf == 1)
		{
			// 在右子树进行的插入
			subRL->_bf = 0;
			subR->_bf = 0;
			parent->_bf = -1;
		}
		else
		{
			assert(false);
		}
	}

	// 先进行左单旋,再进行右单旋
	void RotateLR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		int bf = subLR->_bf;

		RotateL(subL);
		RotateR(parent);

		// 更换平衡因子
		if (bf == 0)
		{
			subL->_bf = subLR->_bf = 0;
		}
		else if (bf == 1)
		{
			// 插入在右子树
			subL->_bf = -1;
			subLR->_bf = 0;
			parent->_bf = 0;
		}
		else if (bf == -1)
		{
			// 插入在左子树
			parent->_bf = 1;
			subL->_bf = 0;
			subLR->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	// 用于检查树的高度
	int TreeHeight(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftheight = TreeHeight(root->_left);
		int rightheight = TreeHeight(root->_right);

		return max(leftheight, rightheight) + 1;
	}

	// 保持树的封装 进行检查AVL树
	bool IsBalance()
	{
		return _IsBalance(root);
	}

	// 进行检查AVL树
	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
			return true;

		int leftheight = TreeHeight(root->_left);
		int rightheight = TreeHeight(root->_right);

		if (rightheight - leftheight != root->_bf)
			return false;

		return abs(rightheight - leftheight) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);
	}


private:
	Node* _root = nullptr;
};


【总结】

AVLTree 高度平衡二叉搜索树。

1.搜索树

2.要求树的左右子树的高度差不超过1,树的左子树和右子树也满足高度差不超过1,(树及所有子树都要满足前面的要求)

3.为了方便实现,引入了平衡因子(这个只是一种方式,并不是必须要这种)

4.平衡因子 = 右子树的高度 - 左子树的高度

5.高度基本可以控制在O(logN)

6.AVLTree增删查改的效率就是O(logN)

7.平衡因子到底是用来干嘛的?

8.AVLTree 的插入三步:

  • 按二叉搜索树方式插入
  • 更新平衡因子
  • 如果更新完,没有违反规定,则插入结束,有违规,则旋转处理

9.不是所有的平衡因子都会被影响,被影响的是祖先,沿着路径往根节点更新祖先节点的平衡因子,但也不是所有的祖先都会被影响。

1.cur 是 parent 的左,parent->bf--,cur 是 parent 的右, parent->bf++,

2.更新完parent的bf 后,如果parent->bf == 0 ,说明parent的高度不变,更新结束,插入完成

解释: 说明更新前,parent 的 bf 是 -1 or 1, 现在变成0, 说明把矮的那边子树给填上了,说明高度没有变,对上层没有影响

3.更新完parent 的 bf 后, 如果parent->bf == 1 or -1 , 说明parent 的高度变了,继续往上更新

解释: 说明跟新前,parent 的 bf 是 0 , 现在变成了 1 or -1, 说明 一边高,对上层有影响,继续往上更新。

4.再次更新完,parent 的 bf === -2 or 2 说明parent所在的子树出现了不平衡,需要旋转处理

5.旋转处理:

  • 旋转完后,还得是搜索树
  • 旋转之后能把它变成平衡(左子树,右子树高度差不超过1)(左单旋,右单旋)

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

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

相关文章

2D 游戏艺术、动画和光照

原文&#xff1a;https://unity.com/resources/2d-game-art-animation-lighting-for-artists-ebook 笔记 用Tilemap瓷砖大小为1单元&#xff0c;人物大小在0.5~2单元 PPU &#xff1a;单位像素 pixels per unit 2160 4K分辨率/ 正交相机size*2 完整屏幕显示像素点 有骨骼动…

4、C#基于.net framework的应用开发实战编程 - 测试(四、二) - 编程手把手系列文章...

四、 测试&#xff1b; 四&#xff0e;二、实际运行&#xff1b; 在应用调试完毕&#xff0c;Bug基本解决的时候就需要对应用进行实际运行来进行查看使用体验及分发的准备工作。 1、 运行设置&#xff1b; 在启动项目上右键属性&#xff0c;点击生成&#xff0c;将顶部的配置改…

栈与队列(C语言版)

文章目录 栈与队列1. 栈基本操作实现(基于链表)代码运行结果 应用场景 2. 队列基本操作实现代码运行结果 应用场景 栈与队列 1. 栈 栈是一种操作受限的线性结构。操作受限体现在&#xff0c;栈只能在一端添加和删除元素&#xff0c;符合后进先出 ( LIFO ) 的特性&#xff0c;…

【算法专场】哈希表

目录 前言 哈希表 1. 两数之和 - 力扣&#xff08;LeetCode&#xff09; 算法分析 算法代码 面试题 01.02. 判定是否互为字符重排 ​编辑算法分析 算法代码 217. 存在重复元素 算法分析 算法代码 219. 存在重复元素 II 算法分析 算法代码 解法二 算法代码 算法…

【设计模式】【行为型模式】迭代器模式(Iterator)

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f44d; 欢迎点赞、收藏、关注&#xff0c;跟上我的更新节奏 &#x1f3b5; 当你的天空突…

mac 意外退出移动硬盘后再次插入移动硬盘不显示怎么办

第一步&#xff1a;sudo ps aux | grep fsck 打开mac控制台输入如下指令&#xff0c;我们看到会出现两个进程&#xff0c;看进程是root的这个 sudo ps aux|grep fsck 第二步&#xff1a;杀死进程 在第一步基础上我们知道不显示u盘的进程是&#xff1a;62319&#xff0c;我们…

如何下载AndroidStudio的依赖的 jar,arr文件到本地

一、通过jitpack.io 下载依赖库 若需要下载 com.github.xxxxx:yy-zzz:0.0.2 的 jar则 https://jitpack.io/com/github/xxxxx/yy-zzz/0.0.2/ 下会列出如下build.logyy-zzz-0.0.2.jaryy-zzz-0.0.2.pomyy-zzz-0.0.2.pom.md5yy-zzz-0.0.2.pom.sha1jar 的下载路径为https://jitpack…

CEF132编译指南 MacOS 篇 - 构建 CEF (六)

1. 引言 经过前面一系列的精心准备&#xff0c;我们已经完成了所有必要的环境配置和源码获取工作。本篇作为 CEF132 编译指南系列的第六篇&#xff0c;将详细介绍如何在 macOS 系统上构建 CEF132。通过配置正确的编译命令和参数&#xff0c;我们将完成 CEF 的构建工作&#xf…

Python大数据可视化:基于python的电影天堂数据可视化_django+hive

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 电影数据 看板展示 我的信息 摘要 电影天堂数据可视化是…

LLM之循环神经网络(RNN)

在人工智能的领域中&#xff0c;神经网络是推动技术发展的核心力量。今天&#xff0c;让我们深入探讨循环神经网络&#xff08;RNN&#xff09; 一、神经网络基础 &#xff08;1&#xff09;什么是神经网络 神经网络&#xff0c;又称人工神经网络&#xff0c;其设计灵感源于人…

Java:204 基于springboot零食销售商城的设计与实现

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 系统主要分为管理员和用户、商家。 用户可以使用网站首页的登录注册界面进行在线登录注册&#xff0c;并且注册登录后方可使用系统的各种功能以及购物…

harmonyOS的文件的增、删、读、写相关操作(fs/content)

注意: 操作harmonyOS的文件只能对app沙箱内的文件进行操作 牵扯到两个支持点: fs和content这两个API; 具体的操作方法看下图: 创建文件 //js 引入 import fs from "ohos.files.fs" import featureAbility from "ohos.ability.featureAbility"; // 上下…

【golang】量化开发学习(一)

均值回归策略简介 均值回归&#xff08;Mean Reversion&#xff09;假设价格会围绕均值波动&#xff0c;当价格偏离均值一定程度后&#xff0c;会回归到均值。 基本逻辑&#xff1a; 计算一段时间内的移动均值&#xff08;如 20 天均线&#xff09;。当当前价格高于均值一定比…

4090单卡挑战DeepSeek r1 671b:尝试量化后的心得的分享

引言&#xff1a; 最近&#xff0c;DeepSeek-R1在完全开源的背景下&#xff0c;与OpenAI的O1推理模型展开了激烈竞争&#xff0c;引发了广泛关注。为了让更多本地用户能够运行DeepSeek&#xff0c;我们成功将R1 671B参数模型从720GB压缩至131GB&#xff0c;减少了80%&#xff…

MySQL数据库(八)☞ 我是不是锁神

目录 1 全局锁的应用 2 索引对行锁的影响 3 表锁&#xff08;显式&#xff09;--表级锁 4 元数据锁 MDL(隐式)--表级锁 5 意向锁(Intention)--IS锁 IX锁--表级锁&#xff08;隐式&#xff09; 6 记录锁-(Record)-S锁 X锁 -- 行级锁 7 如何理解select ... lock in share …

AI法理学与责任归属:技术演进下的法律重构与伦理挑战

文章目录 引言:智能时代的新型法律困境一、AI技术特性对传统法理的冲击1.1 算法黑箱与可解释性悖论1.2 动态学习系统的责任漂移1.3 多智能体协作的责任稀释二、AI法理学的核心争议点2.1 法律主体资格认定2.2 因果关系的技术解构2.3 过错标准的重新定义三、责任归属的实践案例分…

【NLP】循环神经网络RNN

目录 一、认识RNN 二、RNN模型分类 三、传统RNN模型 3.1 结构分析 3.2 Pytorch构建RNN模型 3.3 优缺点 一、认识RNN RNN(Recurrent Neural Network)&#xff0c;中文称作循环神经网络&#xff0c;一般以序列数据为输入&#xff0c;通过网络内部的结构设计有效捕捉序列之…

pnpm, eslint, vue-router4, element-plus, pinia

利用 pnpm 创建 vue3 项目 pnpm 包管理器 - 创建项目 Eslint 配置代码风格(Eslint用于规范纠错&#xff0c;prettier用于美观&#xff09; 在 设置 中配置保存时自动修复 提交前做代码检查 husky是一个 git hooks工具&#xff08;git的钩子工具&#xff0c;可以在特定实际执行特…

Vue的简单入门 一

声明&#xff1a;本版块根据B站学习&#xff0c;创建的是vue3项目&#xff0c;用的是vue2语法风格&#xff0c;仅供初学者学习。 目录 一、Vue项目的创建 1.已安装15.0或更高版本的Node.js 2.创建项目 二、 简单认识目录结构 三、模块语法中的指令 1.v-html 1.文本插值…

VMware Workstate 的 Ubuntu18 安装 vmware tools(不安装没法共享)

在共享主机路径后&#xff0c;可以在&#xff1a; /mnt/hgfs/下方找到共享的文件。但没有安装vmware tool时是没法共享的。 如何安装vmware tool&#xff0c;网上版本很多。这里记录一下&#xff1a; VMware Workstation 17 Pro&#xff0c;版本&#xff1a;17.6.0 虚拟机系统…