AVL树和红黑树

news2024/9/25 3:24:07

AVL树和红黑树

  • 一、AVL树
    • 1. 概念
    • 2. 原理
      • AVL树节点的定义
      • 插入
        • 不违反AVL树性质
        • 违反AVL树性质
          • 左单旋
          • 右单旋
          • 左右双旋
          • 右左双旋
          • 总结
      • 删除
    • 3. 验证代码
    • 4. AVL树完整实现代码
  • 二、红黑树
    • 1. 概念
    • 2. 性质
    • 3. 原理
      • 红黑树节点的定义
      • 默认约定
      • 插入
        • 情况一 (u存在且为红)
        • 情况二(u不存在或u存在且为黑)
      • 删除
    • 4. 相关的验证测试代码
    • 5. 红黑树完整实现代码
  • 三、总结

一、AVL树

1. 概念

AVL树又称高度平衡二叉搜索树:

  1. 它的左右子树都是AVL树
  2. 左右子树高度之差(简称平衡因子)的绝对值不超过1(1, 0, -1)。

AVL树的增删查改的效率

2. 原理

AVL树是在二叉搜索树的基础上引入平衡因子,借助平衡因子调节AVL树高度。(注意:也可以使用高度,来调节AVL树)

AVL树节点的定义

template<class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;   //平衡因子 -- balance factor

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

插入

  1. 按照二叉树的方式插入新节点
  2. 调节平衡因子
    a. 旋转
    b. 结束
不违反AVL树性质

不违反AVL树性质的平衡因子调整的情况:
平衡因子调整的不同情况
根据上述平衡因子的调整情况得出结论:新增节点,影响了被调整子树的高度,需要沿着祖先路径向上调节,直到调节到平衡因子为0的节点或者根节点,只要平衡因子的绝对值不超过1就符合AVL树的性质。

代码实现:

//控制平衡因子
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)
	{
		//树不平衡了,需要旋转
		//...
		break;
	}
	else
	{
		assert(false);
	}
}
违反AVL树性质

违反AVL树性质的平衡因子调整的情况:

这种情况就是平衡因子在调整的过程中出现绝对值大于1的情况 --> 需要进行旋转

左单旋

逻辑分析:
左单旋
代码实现:

void RotateL(Node* parent)
{
	Node* cur = parent->_right;
	Node* curleft = cur->_left;

	//重新链接
	parent->_right = curleft;
	if(curleft)
		curleft->_parent = parent;

	cur->_left = parent;

	//提前保存parent->_parent,可能是根节点,也可能是子树的根节点
	Node* ppnode = parent->_parent;

	parent->_parent = cur;

	if (ppnode == nullptr)
	{
		_root = cur;
		cur->_parent = nullptr; 
	}
	else
	{
		if (ppnode->_left == parent)
		{
			ppnode->_left = cur;
		}
		else
		{
			ppnode->_right = cur;
		}

		cur->_parent = ppnode;
	}

	parent->_bf = cur->_bf = 0;    //根据上面的分析,不平衡的AVL树,在完成旋转后只需要在这两个节点更改平衡因子
}
右单旋

逻辑分析:
右单旋
代码实现:

void RotateR(Node* parent)
{
	Node* cur = parent->_left;
	Node* curright = cur->_right;

	parent->_left = curright;
	if (curright)
		curright->_parent = parent;
	//提前保存parent->_parent,可能是根节点,也可能是子树的根节点
	Node* ppnode = parent->_parent;

	cur->_right = parent;

	parent->_parent = cur;

	if (ppnode == nullptr)
	{
		_root = cur;
		cur->_parent = nullptr;
	}
	else
	{
		if (ppnode->_left == parent)
		{
			ppnode->_left = cur;
		}
		else
		{
			ppnode->_right = cur;
		}

		cur->_parent = ppnode;
	}

	parent->_bf = cur->_bf = 0;
}
左右双旋

逻辑分析:
左右双旋
代码实现:

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

	//在调用完成这两个函数时,改动了parent和cur的影响因子
	RotateL(parent->_left);
	RotateR(parent);

	//调节平衡因子
	if (bf == -1)
	{
		parent->_bf = 1;
		cur->_bf = 0;
		curright->_bf = 0;
	}
	else if (bf == 1)
	{
		parent->_bf = 0;
		cur->_bf = -1;
		curright->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		cur->_bf = 0;
		curright->_bf = 0;
	}
	else
	{
		assert(false);
	}
}
右左双旋

逻辑分析:
右左双旋
基本原理同左右双旋
代码实现:

void RotateRL(Node* parent)
{
	Node* cur = parent->_right;
	Node* curleft = cur->_left;
	int bf = curleft->_bf;

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

	if (bf == 1)
	{
		parent->_bf = -1;
		cur->_bf = 0;
		curleft = 0;
	}
	else if (bf == -1)
	{
		parent->_bf = 0;
		cur->_bf = 1;
		curleft->_bf = 0;
	}
	else if (bf == 0)
	{
		parent->_bf = 0;
		cur->_bf = 0;
		curleft->_bf = 0;
	}
	else
	{
		assert(false);
	}
}
总结

通过parent和cur的平衡因子,来判断是怎么旋转。旋转完成后,高度降低,AVL树平衡,所以不需要继续向上更新

代码:

else if (parent->_bf == 2 || parent->_bf == -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)
	{
		RotateLR(parent);
	}
	else if (parent->_bf == 2 && cur->_bf == -1)
	{
		RotateRL(parent);
	}

	break;
}

删除

类似二叉搜索树的删除。这里不进行介绍

  1. 寻找删除目标(是否存在,存在的位置)
  2. 判断删除目标的结构,使用二叉搜索树的转换思路,将其转为叶子节点
  3. 将删除目标与AVL树断开,调整平衡。

3. 验证代码

验证该树是否是AVL树

代码:

//判断是不是AVLTree
bool IsBalance()
{
	return _IsBalance(_root);
}


//树的高度
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;
}


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

	int leftHight = Height(root->_left);
	int rightHight = Height(root->_right);
	if (rightHight - leftHight != root->_bf)
	{
		cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
		return false;
	}

	return abs(rightHight - leftHight) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);
}

4. AVL树完整实现代码

//AVL树的节点定义
template<class K, class V>
struct AVLTreeNode
{
	pair<K, V> _kv;
	AVLTreeNode<K, V>* _left;
	AVLTreeNode<K, V>* _right;
	AVLTreeNode<K, V>* _parent;
	int _bf;   //平衡因子 -- balance factor

	AVLTreeNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _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->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}

		//链接新增节点
		cur = new Node(kv);
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = 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)
				{
					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);
				}

				break;
			}
			else
			{
				assert(false);
			}
		}

		return true;

	}

	//判断是不是AVLTree
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

private:
	//树的高度
	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;
	}


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

		int leftHight = Height(root->_left);
		int rightHight = Height(root->_right);
		if (rightHight - leftHight != root->_bf)
		{
			cout << "平衡因子异常:" << root->_kv.first << "->" << root->_bf << endl;
			return false;
		}

		return abs(rightHight - leftHight) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);
	}


	//旋转

	void RotateR(Node* parent)
	{
		Node* cur = parent->_left;
		Node* curright = cur->_right;

		parent->_left = curright;
		if (curright)
			curright->_parent = parent;


		Node* ppnode = parent->_parent;

		cur->_right = parent;


		parent->_parent = cur;

		if (ppnode == nullptr)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}

			cur->_parent = ppnode;
		}

		parent->_bf = cur->_bf = 0;
	}

	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;

		//重新链接
		parent->_right = curleft;
		if(curleft)
			curleft->_parent = parent;

		cur->_left = parent;

		//提前保存parent->_parent,可能是根节点,也可能是子树的根节点
		Node* ppnode = parent->_parent;

		parent->_parent = cur;

		if (ppnode == nullptr)
		{
			_root = cur;
			cur->_parent = nullptr; 
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}

			cur->_parent = ppnode;
		}

		parent->_bf = cur->_bf = 0;
	}


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

		//在调用完成这两个函数时,改动了parent和cur的影响因子
		RotateL(parent->_left);
		RotateR(parent);

		//调节平衡因子
		if (bf == -1)
		{
			parent->_bf = 1;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		else if (bf == 1)
		{
			parent->_bf = 0;
			cur->_bf = -1;
			curright->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
			curright->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

	void RotateRL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;
		int bf = curleft->_bf;

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

		if (bf == 1)
		{
			parent->_bf = -1;
			cur->_bf = 0;
			curleft = 0;
		}
		else if (bf == -1)
		{
			parent->_bf = 0;
			cur->_bf = 1;
			curleft->_bf = 0;
		}
		else if (bf == 0)
		{
			parent->_bf = 0;
			cur->_bf = 0;
			curleft->_bf = 0;
		}
		else
		{
			assert(false);
		}
	}

private:
	Node* _root = nullptr;
};

二、红黑树

1. 概念

二叉搜索树的一种,每个节点增加一个存储位表示节点标识的颜色(Red或Black),通过每条路径的每个节点的着色方式的限制,红黑树保证没有一条路径会比其它路径长出两倍。(趋近平衡)

2. 性质

  1. 每个节点只能是红色或黑色
  2. 根节点是黑色
  3. 任何路径没有连续的节点是红色的
  4. 每条路径黑色节点的数量相等

根据性质3和4,保证了红黑树最长路径的节点个数不超过最短路径节点个数的两倍。

eg1(图):
红黑树

eg2(图):证明没有一条路径会比其它路径长出两倍
证明性质

3. 原理

红黑树节点的定义

//节点的颜色
enum Color
{
	RED,    //红
	BLACK   //黑
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Color _color;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_color(RED)
	{}
};

默认约定

新插入的节点,颜色默认为红色

  1. 不需要调整
    如果新插入节点的双亲节点的颜色为黑色,则没有违反红黑树的任何性质。
  2. 需要调整
    当新插入节点的双亲节点颜色为红色,违反了红黑树的性质3。

默认约定1:cur为当前节点,p为双亲节点,g为祖父节点,u为叔叔节点
默认约定2:被调节的树,可能是一颗完整的树,也可能是一颗子树。
eg: (a,b,c,d,e理解成子树即可)该例符合下述插入的情况一
节点的状态

插入

注意: 如果不违反红黑树规则直接寻找位置插入即可,所以这里只介绍违反规则的情况。

违反规则的情况只需要判断u节点的状态。
原因:插入一个节点受影响的节点有cur,p,g,u四个节点,但是前三个节点的颜色是固定的,cur默认是红色。p如果为黑则不违反规则,所以不需要进行调整。g如果为红则证明插入cur之前这棵树就已经不是红黑树。

情况一 (u存在且为红)
  1. 被调节的树是一颗完整的树

是一颗完整的树

  1. 被调节的树是一颗子树
    是一颗子树
情况二(u不存在或u存在且为黑)

这里只对左单旋进行介绍,右单旋情况同理
单旋

这里只对左右双旋进行介绍,右左双旋情况同理
双旋

删除

红黑树的删除原理:根据其子节点的情况进行分类讨论。

  1. 要删除的节点没有子节点,直接将其删除即可。
  2. 要删除的节点只有一个子节点,将其子节点替代要删除的节点,并将替代节点染成黑色,以保持红黑树的性质。
  3. 要删除的节点有两个子节点,需要找到其后继节点(即大于该节点值的最小节点)或前驱节点(即小于该节点值的最大节点)来替代要删除的节点。将后继节点或前驱节点的值复制到要删除的节点,并将要删除的节点指向后继节点或前驱节点。接下来,将删除后继节点或前驱节点的问题转化为删除拥有一个或没有儿子节点的情况。在删除后继节点或前驱节点时,如果删除的节点是黑色的,则可能破坏红黑树的性质。为了维持红黑树的性质,需要进行调整操作。
  4. 根据删除节点的兄弟节点的情况,通过旋转和重新着色等操作重新调整树的结构,即保持红黑树的性质。 在进行旋转和重新着色等调整操作后,最后再删除要删除的节点。

4. 相关的验证测试代码

验证该树是否是红黑树

代码:

//判断该树是不是红黑树
bool IsBalance()
{
	return IsBalance(_root);
}

//计算红黑树的高度
int Height()
{
	return Height(_root);
}


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


//检查颜色
bool CheckColor(Node* root, int blacknum, int benchmark)
{
	if (root == nullptr)
	{
		if (blacknum != benchmark)
		{
			return false;
		}
		return true;
	}

	//计算每条路径的黑色节点
	if (root->_color == BLACK)
	{
		++blacknum;
	}

	if (root->_color == RED && root->_parent && root->_parent->_color == RED)
	{
		cout << root->_kv.first << "出现连续红色节点" << endl;
		return false;
	}


	return CheckColor(root->_left, blacknum, benchmark)
		&& CheckColor(root->_right, blacknum, benchmark);
}


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

	if (root->_color != BLACK)
	{
		return false;
	}

	//基准值 -->  用于比较别的路径黑色节点个数
	int benchmark = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_color == BLACK)
			benchmark++;
		cur = cur->_left;
	}

	return CheckColor(root, 0, benchmark);
}

5. 红黑树完整实现代码

//节点的颜色
enum Color
{
	RED,
	BLACK
};

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Color _color;

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_color(RED)
	{}
};

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	bool Insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_color = BLACK;
			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);
		cur->_color = RED;
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		cur->_parent = parent;


		//调整   这里parent是否为空,是为了下一次循环判断
		//       如果parent->_color == BLACK也不用玩了
		while (parent && parent->_color == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//u为红
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;

					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else //u不存在 或 存在且为黑
				{
					if (cur == parent->_left)
					{
						//      g
						//   p
						//c
						RotateR(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					else
					{
						//      g
						//   p
						//      c	
						RotateL(parent);
						RotateR(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}

					//调整完之后,就不需要继续改变了
					break;
				}
			}
			else   //grandfather->_right == parent
			{
				Node* uncle = grandfather->_left;
				//u为红
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;

					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else //u不存在 或 存在且为黑
				{
					if (cur == parent->_right)
					{
						//g
						//   p
						//      c
						RotateL(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					else
					{
						//g
						//   p
						//c	
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}

					//调整完之后,就不需要继续改变了
					break;
				}
			}
		}

		//根节点的颜色改成黑色
		_root->_color = BLACK;

		return true;
	}

	//判断该树是不是红黑树
	bool IsBalance()
	{
		return IsBalance(_root);
	}

	//计算红黑树的高度
	int Height()
	{
		return Height(_root);
	}

	
private:
	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;
	}


	bool CheckColor(Node* root, int blacknum, int benchmark)
	{
		if (root == nullptr)
		{
			if (blacknum != benchmark)
			{
				return false;
			}
			return true;
		}

		//计算每条路径的黑色节点
		if (root->_color == BLACK)
		{
			++blacknum;
		}

		if (root->_color == RED && root->_parent && root->_parent->_color == RED)
		{
			cout << root->_kv.first << "出现连续红色节点" << endl;
			return false;
		}


		return CheckColor(root->_left, blacknum, benchmark)
			&& CheckColor(root->_right, blacknum, benchmark);
	}


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

		if (root->_color != BLACK)
		{
			return false;
		}

		//基准值 -->  用于比较别的路径黑色节点个数
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_color == BLACK)
				benchmark++;
			cur = cur->_left;
		}

		return CheckColor(root, 0, benchmark);

	}


	//旋转
	//都是二叉树的旋转,所以和AVLTree的旋转一样,只不过这里没有平衡因子
	void RotateR(Node* parent)
	{
		_rotateCount++;


		Node* cur = parent->_left;
		Node* curright = cur->_right;

		parent->_left = curright;
		if (curright)
			curright->_parent = parent;


		Node* ppnode = parent->_parent;

		cur->_right = parent;


		parent->_parent = cur;

		if (ppnode == nullptr)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}

			cur->_parent = ppnode;
		}
	}

	void RotateL(Node* parent)
	{
		_rotateCount++;

		Node* cur = parent->_right;
		Node* curleft = cur->_left;

		//重新链接
		parent->_right = curleft;
		if (curleft)
			curleft->_parent = parent;

		cur->_left = parent;

		//提前保存parent->_parent,可能是根节点,也可能是子树的根节点
		Node* ppnode = parent->_parent;

		parent->_parent = cur;

		if (ppnode == nullptr)
		{
			_root = cur;
			cur->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = cur;
			}
			else
			{
				ppnode->_right = cur;
			}

			cur->_parent = ppnode;
		}

	}


private:
	Node* _root = nullptr;
};

三、总结

红黑树和AVL树都是高效的平衡二叉树,增删查改的效率都是O(logN),红黑树不追求绝对平衡,只需要保证最长路径不超过最短路径的两倍,相对而言降低了旋转次数。所以红黑树更优一些

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

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

相关文章

论文速览 Arxiv 2023 | DMV3D: 单阶段3D生成方法

注1:本文系“最新论文速览”系列之一,致力于简洁清晰地介绍、解读最新的顶会/顶刊论文 论文速览 Arxiv 2023 | DMV3D: DENOISING MULTI-VIEW DIFFUSION USING 3D LARGE RECONSTRUCTION MODEL 使用3D大重建模型来去噪多视图扩散 论文原文:https://arxiv.org/pdf/2311.09217.pdf…

【2017年数据结构真题】

请设计一个算法&#xff0c;将给定的表达式树&#xff08;二叉树&#xff09;转换成等价的中缀表达式&#xff08;通过括号反映次序&#xff09;&#xff0c;并输出。例如&#xff0c;当下列两棵表达式树作为算法的输入时&#xff1a; 输出的等价中缀表达式分别为(ab)(a(-d)) 和…

OpenAI Assistants-API简明教程

OpenAI在11月6号的开发者大会上&#xff0c;除了公布了gpt4-v、gpt-4-turbo等新模型外&#xff0c;还有一个assistants-api&#xff0c;基于assistants-api开发者可以构建自己的AI助手&#xff0c;目前assistants-api有三类的工具可以用。首先就是之前大火的代码解释器(Code In…

隐式转换导致索引失效的原因

Num1 int Num2 varchar Str1不能为null Str2可null 例子1&#xff1a; 结果&#xff1a;124非常快&#xff0c;0.001~0.005秒出结果。3最慢&#xff0c;4~5秒出结果。 查询执行计划&#xff1a;124索引扫描。3全表扫描。 解释&#xff1a;首先四个23都产生隐式转换&#x…

第7天:信息打点-资产泄漏amp;CMS识别amp;Git监控amp;SVNamp;DS_Storeamp;备份

第7天&#xff1a;信息打点-资产泄漏&CMS识别&Git监控&SVN&DS_Store&备份 知识点&#xff1a; 一、cms指纹识别获取方式 网上开源的程序&#xff0c;得到名字就可以搜索直接获取到源码。 cms在线识别&#xff1a; CMS识别&#xff1a;https://www.yun…

【Gradle-13】SNAPSHOT版本检查

1、什么是SNAPSHOT SNAPSHOT版本是指尚未发布的版本&#xff0c;是一个「动态版本」&#xff0c;它始终指向最新的发布工件&#xff08;gav&#xff09;&#xff0c;也就是说同一个SNAPSHOT版本可以反复用来发布。 这种情况在大型app多团队的开发中比较常见&#xff0c;比如us…

【Linux系统化学习】进程的状态 | 僵尸进程 | 孤儿进程

个人主页点击直达&#xff1a;小白不是程序媛 Linux专栏&#xff1a;Linux系统化学习 目录 操作系统进程的状态 运行状态 阻塞状态 进程阻塞的现象 挂起阻塞状态 Linux进程状态 Linux内核源代码怎么说 R&#xff08;running状态&#xff09;运行状态 S&#xff08;sl…

关于DBMS_STATS.GATHER_DATABASE_STATS_JOB_PROC的一些发现

任务在哪 这个是11g以后的自动收集统计信息的后台任务&#xff0c;10g之前是在dba_scheduler_jobs里查看 SQL> SELECT CLIENT_NAME ,STATUS ,MEAN_INCOMING_TASKS_7_DAYS,MEAN_INCOMING_TASKS_30_DAYS FROM DBA_AUTOTASK_CLIENT WHERE…

云课五分钟-0Cg++默认版本和升级-std=c++17

前篇&#xff1a; 云课五分钟-0B快速排序C示例代码-注释和编译指令 视频&#xff1a; 云课五分钟-0Cg默认版本和升级-stdc17 文本&#xff1a; 在Linux系统中&#xff0c;可以通过以下步骤升级g&#xff1a; 打开终端&#xff0c;使用root权限或者sudo权限登录。输入以下命令…

[AI]ChatGPT4 与 ChatGPT3.5 区别有多大

ChatGPT 3.5 注册已经不需要手机了&#xff0c;直接邮箱认证就可以&#xff0c;这可真算是好消息&#xff0c;坏消息是 ChatGPT 4 还是要收费。 那么 GPT-3.5 与 GPT-4 区别有多大呢&#xff0c;下面简单测试一下。 以从 TDengine 订阅数据为例&#xff0c;TDengine 算是不太小…

【C++】一文全解C++中的异常:标准库异常体系&自定义异常体系(含代码演示)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 目录 一.C语言传统的处理错误的方式二.C异常…

python-opencv 培训课程笔记(1)

python-opencv 培训课程笔记&#xff08;1&#xff09; 博主参加了一次opencv库的培训课程&#xff0c;把课程所学整理成笔记&#xff0c;供大家学习&#xff0c;第一次课程包括如下内容&#xff1a; 1.读取图像 2.保存图像 3.使用opencv库显示图像 4.读取图像为灰度图像 …

每天一道算法题(六)——返回一组数字中所有和为 0 且不重复的三元组

文章目录 前言1、问题2、示例3、解决方法4、效果5、注意点 前言 注意&#xff1a;答案中不可以包含重复的三元组。 1、问题 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] n…

基于PHP+MySql的酒店信息管理系统的设计与实现

一、系统开发环境 运行环境&#xff1a;phpstudy或者wampserver&#xff0c; 开发工具&#xff1a;vscodephpstorm 数据库&#xff1a;mysql 二、酒店管理系统功能 1.前台功能&#xff1a; 首页客房推荐&#xff0c;周边特色介绍 酒店在线预订 订单查询&#xff0c;可以…

linux中利用fork复制进程,printf隐藏的缓冲区,写时拷贝技术,进程的逻辑地址与物理地址

1.prinf隐藏的缓冲区 1.思考:为什么会有缓冲区的存在? 2.演示及思考? 1).演示缓存区没有存在感 那为什么我们感觉不到缓冲区的存在呢?我们要打印东西直接就打印了呢? 我们用代码演示一下: 比如打开一个main.c,输入内容如下: #include <stdio.h>int main(){printf…

【机器学习】划分训练集和测试集的方法

在机器学习中&#xff0c;我们的模型建立完成后&#xff0c;通常要根据评估指标来对模型进行评估&#xff0c;以此来判断模型的可用性。而评估指标主要的目的是让模型在未知数据上的预测能力最好。因此&#xff0c;我们在模型训练之前&#xff0c;要对训练集和测试集进行划分。…

python-opencv 培训课程笔记(2)

python-opencv 培训课程笔记&#xff08;2&#xff09; 1.图像格式转换 先看一下cvtColor函数的例子 #默认加载彩图 pathrD:\learn\photo\cv\cat.jpg# imread(path,way) #way0 灰度图。way1 彩图 #默认彩图 imgcv2.imread(path) img_dogcv2.imread(path_dog) #图片格式的转化…

Lesson 04 模板入门

C&#xff1a;渴望力量吗&#xff0c;少年&#xff1f; 文章目录 一、泛型编程1. 引入2. 函数模板&#xff08;1&#xff09;函数模板概念&#xff08;2&#xff09;函数模板格式&#xff08;3&#xff09;函数模板的原理&#xff08;4&#xff09;函数模板的实例化&#xff08…

git rebase 和 git merge的区别?以及你对它们的理解?

文章目录 前言是什么分析区别后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;git操作相关 &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#xff0c;正在不断努力填补技术短板。(如果出现错误&#xff0c;感谢…

MVSNet论文笔记

MVSNet论文笔记 摘要1 引言2 相关基础2.1 多视图立体视觉重建&#xff08;MVS Reconstruction&#xff09;2.2 基于学习的立体视觉&#xff08;Learned Stereo&#xff09;2.3 基于学习的多视图的立体视觉&#xff08;Learned MVS&#xff09; Yao, Y., Luo, Z., Li, S., Fang,…