数据结构 - 红黑树

news2024/11/28 10:38:14

文章目录

    • 前言
    • 一、红黑树介绍
      • 1、红黑树的概念
      • 2、红黑树的性质
    • 二、实现红黑树
      • 1、基本框架
      • 2、插入
      • 3、删除
      • 4、查找
      • 5、测试红黑树
      • 6、红黑树代码
    • 三、红黑树性能
    • 四、AVL树和红黑树的差别


前言

红黑树是一种二叉搜索树,所以学习前需要学会基本的二叉搜索树,并且需要了解左右旋转操作。

一、红黑树介绍

1、红黑树的概念

红黑树是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。
通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路 径会比其他路径长出俩倍,因而是接近平衡的,从而获得较高的查找、插入和删除性能。

2、红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点是黑色的
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

这些性质确保了红黑树从根到叶子的最长路径不会是最短路径的两倍长,从而保持树的相对平衡。

二、实现红黑树

1、基本框架

(1)红黑树节点

//状态
enum Color
{
	BLACK,	//黑色
	RED	//红色
};


//树节点
template<class K,class T>
struct RBTreeNode
{
	RBTreeNode<K,T>* _left;	//左
	RBTreeNode<K, T>* _right;	//右
	RBTreeNode<K, T>* _parent;	//父
	Color _color;	//状态表示
	pair<K, T> _val;	//数据

	//构造函数	状态默认红色
	RBTreeNode(const pair<K, T>& val = pair<K, T>()):_val(val),_left(nullptr),_right(nullptr),
		_parent(nullptr),_color(RED)
	{}
};

(2)红黑树类

template<class K,class T>
class RBTree
{
	//重命名
	typedef RBTreeNode<K, T> Node;

public:
	RBTree() {};
	~RBTree() {};
	bool Insert(const pair<K, T>& val)bool Erase(const K& key);
	Node* Find(const K& key)private:
// 右单旋
void RotateR(Node* parent)
{
	//左节点
	Node* L = parent->_left;
	//左子树右边第一个节点
	Node* Lr = L->_right;
	//parent的父亲
	Node* pparent = parent->_parent;

	//连接过程
	L->_right = parent;
	parent->_parent = L;

	//该节点可能为空
	if (Lr)
	{
		Lr->_parent = parent;
	}
	parent->_left = Lr;

	//更新L的父节点
	L->_parent = pparent;

	//是根的情况
	if (pparent == nullptr)
	{
		_root = L;
	}
	else
	{
		if (parent == pparent->_left) pparent->_left = L;
		else pparent->_right = L;
	}
}
//左旋转
void RotateL(Node* parent)
{
	//右边第一个节点
	Node* R = parent->_right;
	//右子树第一个左节点
	Node* Rl = R->_left;
	//父节点
	Node* pparent = parent->_parent;

	//连接过程

	parent->_right = Rl;
	if (Rl)
	{
		Rl->_parent = parent;
	}

	R->_left = parent;
	//更新parent的父节点
	parent->_parent = R;
	//更新R的父节点
	R->_parent = pparent;
	//是根的情况
	if (nullptr == pparent)
	{
		_root = R;
	}
	else
	{
		if (pparent->_left == parent) pparent->_left = R;
		else pparent->_right = R;
	}

}
	Node* _root;
};

2、插入

红黑树的插入操作是一个复杂但高效的过程,它确保了树在插入新节点后仍然保持平衡。
(1)基本步骤:

1、找到插入位置:与二叉搜索树相同,首先通过比较节点值找到新节点应该插入的位置。
2、插入新节点:将新节点插入到找到的位置,并将其初始颜色设置为红色。这是因为将新节点设置为红色可以最小化对树平衡性的影响,同时满足红黑树的性质(对于破坏性质4来说,破坏性质3代价更小)。
3、调整树以保持平衡:插入红色节点后,可能会破坏红黑树的性质。为了恢复这些性质,需要进行一系列的旋转和重新着色操作。

(2)讨论插入节点后维持树的性质的情况
在这里插入图片描述

情况一:如果插入节点的p为黑色,不做处理也满足红黑树性质,结束。

情况二:p和u都是红色。
分析:通过性质可以推导出g为黑色
解决:将p和u变为黑色,将g变为红色。如果g是根节点,则将其变为黑色。否则,继续对g进行同样的调整。

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/36110af0e1864cbba9252b99458232d2.png

情况三:p为红,u为黑或者为空
分析:
a.如果是u不存在,那么cur一定是新插入的节点,因为如果不是新插入节点,那么cur和p就一定会存在一个黑色节点,那么每条路径的黑色节点就不一致了。
b.如果u存在且为黑,那么cur不是新插入节点且一定是黑,因为p是红u为黑,那么说明p向下这条路径一定存在一个黑色节点,如果cur是新插入就会是红,就会导致原来的树就不满足性质4,如果cur原来是红色节点那么就不满足性质3了(为情况二转变而来)。
c.经过旋转后的树已经是红黑树了,结束。

子情况一:cur、p、g都在一边
解决:都在左边,g变红p变黑,对g使用右旋转(都在右边,g边红p变黑,对g使用左旋转)。
在这里插入图片描述
子情况二:cur和p,p和g不同边
解决:cur和p在右边,p和g在左边,cur变黑,g变红,对p使用左旋转,再对g使用右旋转(:cur和p在左边,p和g在右边,cur变黑,g变红,对p使用右旋转,再对g使用左旋转)。
在这里插入图片描述

3、删除

红黑树是一种自平衡的二叉搜索树,它通过修改节点颜色及执行特定的旋转操作来确保树在添加或删除节点后继续保持平衡。删除操作是红黑树中最复杂的操作之一,因为它需要在删除节点后恢复红黑树的性质。
(1)基本步骤:

1、查找节点: 首先,需要在树中找到需要删除的节点。如果找不到,则直接结束。
2、删除节点:如果找到的节点有两个子节点,通常的做法是用它的后继节点(即右子树中的最小节点)来替换它,并删除原后继节点。这保证了被删除的节点最多只有一个非空子节点。
3、修复树: 删除节点后,需要修复树以保持红黑树的特性: 每个节点是红色或黑色。 根节点是黑色。 每个叶子节点(NIL节点,空节点)是黑色。如果一个节点是红色的,则它的子节点必须是黑色的。 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

(2)讨论删除后恢复树的情况
情况一:cur是红色
解决:直接删除即可,不会影响红黑树的性质,结束。

情况二:cur为黑色,且cur存在一个子节点
分析:cur为黑色,且cur存在一个子节点,那么cur的子节点一定是红色。
解决:p连接cur的子节点,且cur子节点改为黑色,结束。

情况三:cur为黑色且不存在子节点
分析:此时想要不破坏性质就需要观察cur的兄弟节点b了。

子情况一:b为红色
解决:b在p的左侧,b变黑,p变红,再进行右旋转(b在p的右侧,b变黑,p变红,再进行左旋转),变化后仍然没有恢复红黑树的性质,需要再次对cur进行调整(此时可能会转化成其他情况),继续。

在这里插入图片描述

子情况二:b为黑,b的子节点都为空或者孩子节点都为黑
解决:将b改为红,如果p为红将p改为黑结束,如果p不为红,进行迭代,cur = p,继续。
在这里插入图片描述
子情况三:b为黑且存在一个红色子节点(剩余的一个子树任意)
假设:cur在p的左侧且b的右节点br为红
解决:将b变为p的颜色,p和br变为黑色,再对p左旋转,旋转完红黑树恢复,结束。
在这里插入图片描述
假设:cur在p的右侧且b的左节点bl为红
解决:将b变为p的颜色,p和bl变为黑色,再对p右旋,旋转完红黑树恢复,结束。
在这里插入图片描述
假设:cur在p的左侧且b的左节点bl为红
解决:将bl变为p的颜色,p和b变为黑色,先进行右旋转b再进行p左旋,旋转完红黑树恢复,结束。
在这里插入图片描述

假设:cur在p的右侧且b的右节点br为红
解决:将br变为p的颜色,p和b变为黑色,先进行对b左旋再进行p右旋,旋转完红黑树恢复,结束。
在这里插入图片描述

4、查找

红黑树查找与二叉搜索树一样。
步骤:

1、开始于根节点:查找操作从树的根节点开始。 比较节点值:将查找值与当前节点的值进行比较。
2、
如果查找值等于当前节点的值,则查找成功,返回该节点。
如果查找值小于当前节点的值,则移动到左子节点。
如果查找值大于当前节点的值,则移动到右子节点。

3、重复步骤2:继续比较并移动,直到找到包含查找值的节点,或者遇到一个叶子节点(NIL节点),这意味着查找值不在树中。
4、查找结束:如果找到包含查找值的节点,则返回该节点;否则,返回NULL或者一个特殊的值表示查找失败。

5、测试红黑树

通过判断根节点是否为黑+每一条路径黑色节点数量是否一样+是否存在父子都为红。
测试代码:

bool IsBalance()
{
	if (_root == nullptr)
		return true;

	if (_root->_color == RED)
	{
		return false;
	}

	// 参考值
	int refNum = 0;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_color == BLACK)
		{
			++refNum;
		}

		cur = cur->_left;
	}

	return Check(_root, 0, refNum);
}
bool Check(Node* root, int blackNum, const int refNum)
{
	if (root == nullptr)
	{
		//cout << blackNum << endl;
		if (refNum != blackNum)
		{
			cout << "存在黑色节点的数量不相等的路径" << endl;
			return false;
		}

		return true;
	}

	//cout << root->_val.first << endl;
	if (root->_color == RED && root->_parent->_color == RED)
	{
		cout << root->_val.first << "存在连续的红色节点" << endl;
		return false;
	}

	if (root->_color == BLACK)
	{
		blackNum++;
	}

	return Check(root->_left, blackNum, refNum)
		&& Check(root->_right, blackNum, refNum);
}
void test()
{
	RBTree<int, int> rb;
	vector<int> arr;
	for (int i = 1; i <= 10000; i++)
	{
		arr.push_back(rand());
	}

	for (int i = 0; i < arr.size(); i++)
	{
		int e = arr[i];
		rb.Insert({ e,e });
		if (rb.IsBalance())
			cout << "是红黑树" << endl;
		else
			assert(false);
	}

	for (int i = 0; i < arr.size(); i++)
	{
		rb.Erase(arr[i]);
		if (rb.IsBalance())
			cout << "是红黑树" << endl;
		else
			assert(false);
	}
}

通过插入10000随机数观察是否报错。
在这里插入图片描述
在运行结束后仍然没有报错说明插入、删除没有问题。

6、红黑树代码

#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;

//状态
enum Color
{
	BLACK,	//黑色
	RED	//红色
};


//树节点
template<class K,class T>
struct RBTreeNode
{
	RBTreeNode<K,T>* _left;	//左
	RBTreeNode<K, T>* _right;	//右
	RBTreeNode<K, T>* _parent;	//父
	Color _color;	//状态表示
	pair<K, T> _val;	//数据

	//构造函数	状态默认红色
	RBTreeNode(const pair<K, T>& val = pair<K, T>()):_val(val),_left(nullptr),_right(nullptr),
		_parent(nullptr),_color(RED)
	{}
};

template<class K,class T>
class RBTree
{
	//重命名
	typedef RBTreeNode<K, T> Node;

public:
	RBTree() {};
	~RBTree() {};

	//插入
	bool Insert(const pair<K, T>& val)
	{
		//找到放val的位置
		Node* cur = _root;
		//作为前驱指针,与val节点连接
		Node* parent = nullptr;
		while (cur)
		{
			//向左
			if (cur->_val.first > val.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			//向右
			else if (cur->_val.first < val.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			//存在相同的值
			else
			{
				return false;
			}
		}

		//插入新节点
		cur = new Node(val);
		//不存在根节点,作为根节点
		if (parent == nullptr) _root = cur;
		//连接在前驱指针左侧
		else if (parent->_val.first > val.first)
		{
			cur->_parent = parent;
			parent->_left = cur;
		}
		//连接在前驱指针右侧
		else
		{
			cur->_parent = parent;
			parent->_right = cur;
		}

		//父亲不为空且孩子和父亲都为红 --- 违规规则三
		while (parent != nullptr &&parent->_color == RED && cur->_color == RED)
		{
			Node* grandfather = parent->_parent;	//祖父

			Node* uncle = nullptr;	//叔叔
			if (grandfather->_left == parent)
				uncle = grandfather->_right;
			else
				uncle = grandfather->_left;
			
			//情况一 uncle存在且为红 - 祖父变红父亲和叔叔变黑,向上更新
			if (uncle != nullptr && uncle->_color == RED)
			{
				parent->_color = uncle->_color = BLACK;
				grandfather->_color = RED;
			}

			//情况二 uncle不存在\存在
			else
			{
				//子情况一,cur 、parent在一边
				//都在左边 -- 使用右旋转
				if (grandfather->_left == parent && parent->_left == cur)
				{
					RotateR(grandfather);
					grandfather->_color = RED;
					parent->_color = BLACK;
				}
				//都在右边 --- 使用左旋转
				else if (grandfather->_right == parent && parent->_right == cur)
				{
					RotateL(grandfather);
					grandfather->_color = RED;
					parent->_color = BLACK;
				}
				//子情况二	cur与parent不再同一边
				//cur在右边parent在左边  --对parent左旋转再对grandfather右旋转
				else if (grandfather->_left == parent && parent->_right == cur)
				{
					RotateL(parent);
					RotateR(grandfather);
					cur->_color = BLACK;
					grandfather->_color = RED;
				}
				//cur在左边parent在右边  --对parent右旋转再对grandfather左旋转
				else if (grandfather->_right == parent && parent->_left == cur)
				{
					
					RotateR(parent);
					RotateL(grandfather);
					cur->_color = BLACK;
					grandfather->_color = RED;
				}

				//经过旋转后树达到平衡
				break;

			}

			//迭代
			cur = grandfather;
			parent = cur->_parent;
		}

		//根节点一定是黑色的
		_root->_color = BLACK;
		return true;
	}


	//删除
	bool Erase(const K& key)
	{
		//从根节点开始搜索
		Node* cur = _root;
		//作为cur的前驱指针
		Node* parent = nullptr;

		//搜索查找
		while (cur)
		{
			if (cur->_val.first > key)
			{
				parent = cur;
				cur = cur->_left;
			}

			else if (cur->_val.first < key)
			{
				parent = cur;
				cur = cur->_right;
			}

			else
				break;
		}

		//找不到
		if (cur == nullptr) return false;

		//假设cur左右节点都存在,找右边最小值替换
		if (cur->_left != nullptr && cur->_right != nullptr)
		{
			Node* tmp1 = cur->_right;
			Node* tmp2 = nullptr;

			while (tmp1->_left)
			{
				tmp2 = tmp1;
				tmp1 = tmp1->_left;
			}

			cur->_val = tmp1->_val;
			//tmp1左边没有节点,自己就是最小的节点
			if (tmp2 == nullptr)
			{
				parent = cur;
				cur = tmp1;
			}
			else
			{
				cur = tmp1;
				parent = tmp2;
			}
		}

		//如果是cur根节点
		if (parent == nullptr)
		{
			if (cur->_left == nullptr)
			{
				_root = cur->_right;
				if (_root != nullptr)
					_root->_color = BLACK;
			}
			else
			{
				_root = cur->_left;
				_root->_color = BLACK;
			}
			delete cur;
			return true;
		}

		//标记要删除的节点
		Node* deletecur = cur;

		//删除节点为红色,直接删除结束
		if (cur->_color == RED)
		{
			if (cur->_left == nullptr)
			{
				if (cur == parent->_left)	parent->_left = cur->_right;
				else parent->_right = cur->_right;
			}
			else
			{
				if (cur == parent->_right)	parent->_right = cur->_left;
				else parent->_right = cur->_left;
			}
		}
		//删除节点为黑色
		else
		{
			//存在左子树,变为黑色
			if (cur->_left != nullptr)
			{
				cur->_left->_color = BLACK;
				if (cur == parent->_right)
					parent->_right = cur->_left;
				else
					 parent->_left = cur->_left;

				cur->_left->_parent = parent;
			}
			//存在右子树
			else if (cur->_right != nullptr)
			{
				cur->_right->_color = BLACK;

				if (cur == parent->_left)	
					parent->_left = cur->_right;
				else
					parent->_right = cur->_right;

				cur->_right->_parent = parent;
			}
			//不存在孩子
			else
			{
				while (parent != nullptr)
				{
					int sign = 0;
					if (cur == parent->_left) sign = -1;
					else sign = 1;

					if (cur == deletecur)
					{
						if (sign == -1) parent->_left = nullptr;
						else parent->_right = nullptr;
					}

					//兄弟节点
					Node* brothers = nullptr;
					if (sign == -1) brothers = parent->_right;
					else brothers = parent->_left;

					//兄弟节点为红色
					if (brothers->_color == RED)
					{
						//变色
						brothers->_color = BLACK;
						parent->_color = RED;
						//在右边-》左转
						if (brothers == parent->_right)
						{
							RotateL(parent);
						}
						//在左边-》右转
						else
						{
							RotateR(parent);
						}
						//重新更新brothers
						if (sign == -1) brothers = parent->_right;
						else brothers = parent->_left;
					}

					//兄弟节点的孩子节点为空或者孩子节点为黑
					if ((brothers->_left == nullptr || brothers->_left->_color == BLACK) && 
						(brothers->_right == nullptr || brothers->_right->_color == BLACK))
						
					{
						brothers->_color = RED;
						//父亲为红色时直接结束
						if (parent->_color == RED)
						{
							parent->_color = BLACK;
							break;
						}
						//更新继续
						cur = parent;
						parent = parent->_parent;
					}
					//兄弟节点不为空有一个孩子为红或者都为红
					else
					{
						//cur作为parend的左孩子
						if (sign == -1)
						{
							//作为brothers右孩子--左旋
							if (brothers->_right && brothers->_right->_color == RED)
							{
								brothers->_color = parent->_color;
								parent->_color = brothers->_right->_color = BLACK;
								RotateL(parent);
							}
							//作为brothers左孩子,先右旋再左旋
							else if (brothers->_left && brothers->_left->_color== RED)
							{
								brothers->_left->_color = parent->_color;
								brothers->_color = parent->_color = BLACK;			
								RotateR(brothers);
								RotateL(parent);
							}
						}
						else
						{
							//作为brothers左孩子--右旋
							if (brothers->_left&&brothers->_left->_color == RED)
							{
								brothers->_color = parent->_color;
								parent->_color = brothers->_left->_color = BLACK;
								RotateR(parent);
							}
							//作为brothers右孩子--先左旋再右旋
							else if (brothers->_right && brothers->_right->_color == RED)
							{
								brothers->_right->_color = parent->_color;
								brothers->_color = parent->_color = BLACK;
								RotateL(brothers);
								RotateR(parent);
							}
						}
						//再旋转完后性质恢复,结束
						break;
					}
				}
			}
		}

		delete deletecur;
		return true;
	}

	//查找
	Node* Find(const K& key)
	{
		//从根节点开始
		Node* cur = _root;
		while (cur)
		{
			//大了就去左子树中搜索
			if (cur->_val.first > key)
			{
				cur = cur->_left;
			}
			//小了就去右子树中搜索
			else if (cur->_val.first < key)
			{
				cur = cur->_right;
			}
			else
			{
				//找到返回当前节点
				return cur;
			}
		}
		return nullptr;
	}
	bool IsBalance()
	{
		if (_root == nullptr)
			return true;

		if (_root->_color == RED)
		{
			return false;
		}

		// 参考值
		int refNum = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_color == BLACK)
			{
				++refNum;
			}

			cur = cur->_left;
		}

		return Check(_root, 0, refNum);
	}
private:
	// 右单旋
	void RotateR(Node* parent)
	{
		//左节点
		Node* L = parent->_left;
		//左子树右边第一个节点
		Node* Lr = L->_right;
		//parent的父亲
		Node* pparent = parent->_parent;

		//连接过程
		L->_right = parent;
		parent->_parent = L;

		//该节点可能为空
		if (Lr)
		{
			Lr->_parent = parent;
		}
		parent->_left = Lr;

		//更新L的父节点
		L->_parent = pparent;

		//是根的情况
		if (pparent == nullptr)
		{
			_root = L;
		}
		else
		{
			if (parent == pparent->_left) pparent->_left = L;
			else pparent->_right = L;
		}
	}

	//左旋转
	void RotateL(Node* parent)
	{
		//右边第一个节点
		Node* R = parent->_right;
		//右子树第一个左节点
		Node* Rl = R->_left;
		//父节点
		Node* pparent = parent->_parent;

		//连接过程

		parent->_right = Rl;
		if (Rl)
		{
			Rl->_parent = parent;
		}

		R->_left = parent;
		//更新parent的父节点
		parent->_parent = R;
		//更新R的父节点
		R->_parent = pparent;
		//是根的情况
		if (nullptr == pparent)
		{
			_root = R;
		}
		else
		{
			if (pparent->_left == parent) pparent->_left = R;
			else pparent->_right = R;
		}

	}
	
	bool Check(Node* root, int blackNum, const int refNum)
	{
		if (root == nullptr)
		{
			//cout << blackNum << endl;
			if (refNum != blackNum)
			{
				cout << "存在黑色节点的数量不相等的路径" << endl;
				return false;
			}

			return true;
		}

		//cout << root->_val.first << endl;
		if (root->_color == RED && root->_parent->_color == RED)
		{
			cout << root->_val.first << "存在连续的红色节点" << endl;
			return false;
		}

		if (root->_color == BLACK)
		{
			blackNum++;
		}

		return Check(root->_left, blackNum, refNum)
			&& Check(root->_right, blackNum, refNum);
	}

	Node* _root;
};

三、红黑树性能

  1. 时间复杂度 查找、插入和删除操作:红黑树通过特定的旋转和重新着色操作来保持树的平衡,从而确保这些操作的时间复杂度保持在O(log n),其中n是树中元素的数目。这使得红黑树在大数据集上仍然能够保持高效的性能。
  2. 平衡性 平衡因子:与AVL树不同,红黑树并不直接维护每个节点的平衡因子(左子树高度减去右子树高度)。相反,它通过维护一系列颜色相关的性质来间接保持树的平衡。这些性质包括节点的颜色(红色或黑色)、根节点是黑色、所有叶子节点是黑色、红色节点的两个子节点都是黑色等。
    路径长度限制:红黑树的这些性质确保了从根到任意叶子节点的最长路径不会超过最短路径的两倍。这种平衡性保证了在最坏情况下,查找、插入和删除操作的时间复杂度仍然是O(log
    n)。
  3. 插入和删除操作的性能 插入操作:在插入新节点时,红黑树会首先将其标记为红色,并通过一系列的旋转和重新着色操作来恢复树的平衡。由于红黑树的自平衡策略相对宽松,因此在插入操作中通常比AVL树具有更好的性能。
    删除操作:删除操作可能稍微复杂一些,因为它涉及到节点的删除以及后续可能的旋转和重新着色操作来恢复树的平衡。然而,与插入操作类似,删除操作的时间复杂度也保持在O(log
    n)。

四、AVL树和红黑树的差别

  1. 调整平衡的实现机制 AVL树:AVL树通过维护每个节点的平衡因子(左子树高度减去右子树高度)来实现平衡。在插入或删除节点后,如果某个节点的平衡因子绝对值大于1,则需要通过旋转操作来恢复平衡。AVL树的平衡条件是严格的,即所有节点的左右子树高度差的绝对值不超过1。
    红黑树:红黑树则通过节点的颜色(红色或黑色)和一系列旋转操作来保持树的平衡。红黑树的平衡条件相对宽松,它要求从根节点到叶子节点的所有路径上黑色节点的数量相同,从而间接保证了树的平衡性。在插入或删除节点后,红黑树通过重新着色和旋转操作来恢复平衡,且任何不平衡都可以在三次旋转之内解决。
  2. 性能差异 插入和删除操作:由于AVL树需要保持严格的平衡性,因此在插入或删除节点时可能需要进行更多的旋转操作,这在一定程度上降低了其性能。相比之下,红黑树通过非严格的平衡条件换取了更少的旋转次数,从而在插入和删除操作上表现出更高的效率。
    查找操作:在查找操作上,AVL树和红黑树的性能相当。它们都保持了二叉查找树的性质,可以在对数时间内完成查找操作。然而,由于AVL树的高度通常比红黑树更低(在相同节点数的情况下),因此从理论上讲,AVL树的查找效率可能会略高于红黑树,但这种差异在实际应用中往往可以忽略不计。
  3. 内存占用 AVL树需要存储每个节点的平衡因子,这增加了额外的内存开销。 红黑树则通过节点的颜色属性来间接维护平衡性,无需存储额外的平衡因子信息,因此在内存占用上更为节省。
  4. 适用场景 AVL树:适用于查找操作频繁且对内存占用要求不高的场景。由于AVL树保持了严格的平衡性,因此在查找性能上具有一定优势。然而,其插入和删除操作的性能相对较低,不适合数据变动频繁的场景。
    红黑树:适用于插入和删除操作频繁且对查找性能要求不是极端严格的场景。红黑树通过牺牲一定的平衡性来换取更高的插入和删除效率,因此在这些场景下表现出更好的整体性能。此外,红黑树的内存占用更低,也使其在一些内存受限的环境中更具优势。
    综上所述,AVL树和红黑树在调整平衡的实现机制、性能差异、内存占用和适用场景等方面存在显著的区别。在选择使用哪种数据结构时,需要根据具体的应用场景和需求进行综合考虑。

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

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

相关文章

超越源自内省:《自卑与超越》

作者主页&#xff1a; &#x1f517;进朱者赤的博客 精选专栏&#xff1a;&#x1f517;经典算法 作者简介&#xff1a;阿里非典型程序员一枚 &#xff0c;记录在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法&#xff08;公众号同名&#xff09; ❤️觉得文章还…

JS 改造数组对象,将其不确定的key作为value,并合并相同key的value值

const data [{"苹果": 3839,"小米": 1423,"华为": 4965,"oppo": 3334,"vivo": 2820,"一加": 4751},{"苹果": 3560,"小米": 2099,"华为": 3192,"oppo": 4210,"vivo…

anaconda 安装

环境 win 11 安装 下载安装包 https://www.anaconda.com/download 2.打开安装包&#xff0c;这里基本上就是下一步下一步 3.配置环境变量 对应自己安装的目录文件夹 D:\anaconda3 D:\anaconda3\Scripts D:\anaconda3\Library\bin4.打开命令行测试 常用命令 初始化 co…

python毕业设计选题求职招聘系统-可视化大屏

✌网站介绍&#xff1a;✌10年项目辅导经验、专注于计算机技术领域学生项目实战辅导。 ✌服务范围&#xff1a;Java(SpringBoo/SSM)、Python、PHP、Nodejs、爬虫、数据可视化、小程序、安卓app、大数据等设计与开发。 ✌服务内容&#xff1a;免费功能设计、免费提供开题答辩P…

【限免】16PAM、16PSK、16QAM、16CQAM星座图及误码率【附MATLAB代码】

​微信公众号&#xff1a;智能电磁频谱算法 QQ交流群&#xff1a;949444104 主要内容 MATLAB代码 % Parameters M 16; N 4; % Number of circles for CQAM SNR_dB 0:2:25; % Extended SNR range to reach higher values num_symbols 1e5; % Total number of symbols for s…

静态路由学习笔记

1. 静态路由应用场景 &#xff08;1&#xff09;静态路由由网络管理员手动配置&#xff0c;配置方便&#xff0c;对系统要求低&#xff0c;适用于拓扑结构简单并且稳定的小型网络。 &#xff08;2&#xff09;缺点是不能自动适应网络拓扑的变化&#xff0c;需要人工干预过多。…

保险丝(常见元器件及电路基础知识)

分类&#xff1a;简单分为熔断式和非熔断式 电压&#xff1a;保险丝的额定电压是指它的公称额定电压, 通常就是保险丝断开后能够承受的最大电压值保险丝通电时两端所承受的电压大大小于其额定电压&#xff0c;因此额定电压基本上无关紧要。 电流&#xff1a; PFC为功率因数矫…

Java之开发 系统设计 分布式 高性能 高可用

1、restful api 基于rest构建的api 规范&#xff1a; post delete put get 增删改查路径 接口命名 过滤信息状态码 2、软件开发流程 3、命名规范 类名&#xff1a;大驼峰方法名&#xff1a;小驼峰成员变量、局部变量&#xff1a;小驼峰测试方法名&#xff1a;蛇形命名 下划…

语言模型及数据集

一、定义 1、语言模型的目标是估计序列的联合概率&#xff0c;一个理想的语言模型就能够基于模型本身生成自然文本。 2、对一个文档&#xff08;词元&#xff09;序列进行建模&#xff0c; 假设在单词级别对文本数据进行词元化。 3、计数建模 &#xff08;1&#xff09;其中…

AI绘画;喂饭进阶!教你如何用Stable Diffusion生成高清建筑手工模型图,一篇文章搞懂什么是Lora模型和CKPT主模型!

前言 刚接触Stable Diffusion不久的你&#xff0c;是否有这样的疑问&#xff1a; Q1: Stable Diffusion中的主模型CKPT是什么&#xff1f; Q2: Stable Diffusion中的Lora模型又是什么&#xff1f; Q3: 在哪儿可以下载好用的AI绘图模型&#xff1f; Q4: Stable Diffusion 如…

【SpringBoot】2 项目搭建

创建项目 1&#xff09;确实本地 jdk 版本 打开命令行窗口&#xff1a;快捷键 Windows R&#xff0c;输入 CMD&#xff0c;敲回车 执行命令&#xff1a;java -version 2&#xff09;在项目 clone 的位置创建 Spring Boot 项目&#xff0c;使用 Maven 进行依赖管理&#xff…

最新爆火的开源AI项目 | LivePortrait 本地安装教程

LivePortrait 本地部署教程&#xff0c;强大且开源的可控人像AI视频生成 1&#xff0c;准备工作&#xff0c;本地下载代码并准备环境&#xff0c;运行命令前需安装git 以下操作不要安装在C盘和容量较小的硬盘&#xff0c;可以找个大点的硬盘装哟 2&#xff0c;需要安装FFmp…

大疆创新2025校招内推

大疆2025校招-内推 一、我们是谁&#xff1f; 大疆研发软件团队&#xff0c;致力于把大疆的硬件设备和大疆用户紧密连接在一起&#xff0c;我们的使命是“让机器有温度&#xff0c;让数据会说话”。 在消费和手持团队&#xff0c;我们的温度来自于激发用户灵感并助力用户创作…

聊聊基于Alink库的主成分分析(PCA)

概述 主成分分析&#xff08;Principal Component Analysis&#xff0c;PCA&#xff09;是一种常用的数据降维和特征提取技术&#xff0c;用于将高维数据转换为低维的特征空间。其目标是通过线性变换将原始特征转化为一组新的互相无关的变量&#xff0c;这些新变量称为主成分&…

react中useMemo钩子函数的使用

1.使用useMemo前展示 import { useState,useMemo } from "react"function kanno(num){console.log(999,num);return num }function UseMemo(){const [count1,setCount1] useState(0)const [count2,setCount2] useState(0)const result kanno(count1)console.log(…

ELK安装(Elasticsearch+Logstash+Kibana+Filebeat)

一、简介 1.1、软件简介 ELK其实是Elasticsearch&#xff0c;Logstash 和 Kibana三个产品的首字母缩写&#xff0c;这三款都是开源产品。 1.1.1、Elasticsearch简介 Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它能很方便的使大量数据具有搜索、分析…

magento2 安装win环境和linux环境

win10 安装 安装前提&#xff0c;php,mysql,apach 或nginx 提前安装好 并且要php配置文件里&#xff0c;php.ini 把错误打开 display_errorsOn开始安装 检查环境 填写数据库信息 和ssl信息&#xff0c;如果ssl信息没有&#xff0c;则可以忽略 填写域名和后台地址&#xff0…

花几千上万学习Java,真没必要!(二十九)

1、基本数据类型包装类&#xff1a; 测试代码1&#xff1a; package apitest.com; //使用Integer类的不同方法处理整数。 //将字符串转换为整数&#xff08;parseInt&#xff09;和Integer对象&#xff08;valueOf&#xff09;&#xff0c; //将整数转换回字符串&#xff08;…

Linux下使用gdb进行调试入门级

个人名片&#xff1a; &#x1f393;作者简介&#xff1a;嵌入式领域优质创作者&#x1f310;个人主页&#xff1a;妄北y &#x1f4de;个人QQ&#xff1a;2061314755 &#x1f48c;个人邮箱&#xff1a;[mailto:2061314755qq.com] &#x1f4f1;个人微信&#xff1a;Vir2025WB…

网络通信---UDP

前两天做了个mplayer项目&#xff0c;今日继续学习 网络内容十分重要&#xff01;&#xff01;&#xff01; 1.OSI七层模型 应用层:要传输的数据信息&#xff0c;如文件传输&#xff0c;电子邮件等&#xff08;最接近用户&#xff0c;看传输的内容类型到底是什么&#xff09; …