草庐IT

【数据结构】红黑树

一只大喵咪1201 2023-08-07 原文

🐱作者:一只大喵咪1201
🐱专栏:《数据结构与算法》
🔥格言:你只管努力,剩下的交给时间!

在学习AVL树的时候,我们知道,当修改AVL树的结构(插入,删除)时,会通过旋转来保证平衡因子不超过1,所以频繁的修改结构会导致效率低下,今天我们学习的红黑树就完美解决了这个问题。

红黑树

🍧红黑树

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

红黑树也是一种二叉搜索树,和AVL树一样,也是在二叉搜索树的基础上做一些限制来保证它的搜索效率。

  • 红黑树原则: 最长路径不超过最短路径的二倍

红黑树就是靠这个原则来维护它的结构的。

  • 上图所示,最长路径是13->17->25->27->NULL(不止这一条),路径长度是4。
  • 最短路径是13->8->NULL(不止这一条),路径长度是2。

此时就是红黑树的极限了,它相对于AVL树平衡性较低,根节点的左右子树高度差是2。但是它比AVL树修改结构时的效率高。

为了维护最长路径不超过最短路径的2倍这一原则,红黑树是通过几个性质来达到目的的:

  • 每个节点不是红色就是黑色。
  • 整颗树的根节点是黑色的。
  • 如果一个节点是红色的,则它的两个孩子节点必须是黑色的(不会出现两个连续的红色节点)。
  • 从任意节点到其所有后代节点的简单路径上,均包含相同数目的黑色节点。
  • 每个叶子节点都是黑的(此处的叶子节点特指空节点NULL)。

这里重点解释两条性质:

  1. 不会出现两个连续的红色节点。


如上图所示,节点8的左右子节点必须都是黑色的,不能出现红色。

  1. 任意节点到其后代所有节点的简单路径上,黑色节点的个数相同。

  • 简单路径:13->17->15->NULL,如上图绿色框。
  • 复杂路径:13->8-1->NULL->6->NULL,如上图蓝色框。

复杂路径采用的是前序遍历的方式,而简单路径只是从某一节点出发,直接到其叶子节点。

  • 红色框中的路径,黑色节点有两个(不算空节点NULL)。
  • 蓝色框中的路径,合适节点也是有两个(不算空节点NULL)。

从任意节点出发到其所有叶子节点的简单路径,黑色节点个数都是相同的,包不包括空节点NULL都可以,因为所有叶子节点都有空节点NULL,默认情况下我们不包括空节点

红黑树最优情况(左右最平衡):

  • 此时是最优情况,此时的平衡度最高。

最优情况:

  • 全黑或者每条路径都是一黑一红相间的路径,并且是一颗满二叉树。

红黑树最差情况(左右最不平衡):

  • 此时情况是最差的,最长的路径是一黑一红,最短路径都是黑,平衡度是最低的。

最差情况:

  • 左子树全黑,右子树一黑一红相间,或者反过来。
  • 最短路径的长度:log2(N+1),等价于log2N。
  • 最长路径的长度:2*log2N。

具化一下,十亿个数据,最短路径包含的个数大约是30个,而最长路径也不过是60个左右。对于计算机而言,30个和60个几乎是一样的,所以它们的搜索效率也可以看成是一样的。

这时反过来再看:

  • 如果可以出现两个连续的红色节点,就不能保证最长路径不超过最短路径的二倍这一原则了。
  • 如果不同简单路径上的黑色节点个数可以不一样,同样不能保证最长路径不超过最短路径的二倍这一原则了。

AVL树维持平衡非常直接,直接通过旋转来维持左右子树高度差不超过1这个原则。而红黑树就不是这么直接了,它是通过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 _col;//颜色

	//构造函数
	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_col(RED)//新节点默认是红色
	{}
};
  • 节点颜色是枚举变量,定义枚举时,只有红色REB和黑色BLACK。
  • 构造函数中,节点的默认颜色给成红色。

🍧红黑树的插入

插入节点的过程和二叉搜索树一样,不同在于插入以后要进行调整,此时的调整是按照红黑树的规则进行调整,先来看插入:

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->_col = 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->_col = RED;//新节点颜色给成红色
		//插入新节点
		//比父节点大,插入到右边
		if (parent->_kv.first < kv.first)
		{
			parent->_right = cur;
			cur->_parent = parent;
		}
		//比父节点小,插入到左边
		else if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}

		//调整新节点

		//插入成功
		return true;
	}
	//中序打印
	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);
	}
protected:
	Node* _root = nullptr;
};

  • 当插入的新节点作根节点时,需要将该节点的颜色变成黑色,符合红黑树的性质,因为默认情况下节点的颜色是红色的(节点构造函数的初始化中)。

  • 在插入节点的时候,需要确保新节点的颜色是红色,当然这一步可以省略,因为我们在构造节点时就默认给了它红色,这里是为了严谨。

这是为什么呢?新节点是黑的又会怎样呢?

  • 插入的新节点是黑的,必然会违背每条简单路径上黑色节点个数相同这一性质,因为别的路径上黑色节点数量没变。
  • 插入的新节点是红的,有可能会违背不能有两个连续的红色节点这一性质。

此时我们选择哪种呢?当然是选择代价小的。

  • 新插入节点是红色,有可能违背不能有两个连续红节点的原则,如果违背了,只需要调整这一个路径上节点的颜色。如果没有违背都不用进行调整。
  • 新插入节点是黑色的,必然违背每条简单路径黑色节点数相同的原则,需要调整每一条路径。

相对来说,插入新节点的颜色是红色代价比较小

这部分代码在二叉搜索树和AVL树中都有,本喵就不再讲解了,此时的是仅仅是一颗二叉搜索树,接下来就需要我们对这颗树进行调整,使它成为红黑树。

🍧红黑树的调整

调整就是要维护最长路径不超过最短路径的二倍这一原则,但是并不是直接去达到这个目的,而是通过维护红黑树的那几条性质来间接达到这个目的。

需要调整的情况可以分为三种:

🍨cur为红,p为红,g为黑,u存在且为红

  • cur:表示当前节点,可能是新插入节点,也可能是待调整节点。
  • p:表示父节点,是单词parent的首字母。
  • g:表示祖父节点,是grandfather的首字母。
  • u:表示叔叔节点,是uncle的首字母。

先来看具象图:

cur是新插入的节点(红色):

  • 新插入节点cur是红色,违背了不能有两个连续红色节点的性质。节点g到其两个叶子节点的简单路径中,黑色节点个数都是1。

调整过程:

  • 先将节点p和u变成黑色。
  • 再将节点g变成红色。
  • 更新亲戚关系。

此时从节点g到其两个叶子节点的简单路径上,黑色节点仍然都是1,而且没有两个连续的红色节点。

在上图基础上增加两层节点:

最开始的cur是新插入节点,c/d/e都是下面红色框中三种中的一种,此时从根节点到其所有叶子节点的简单路径上,黑色节点的个数都是2。

调整过程:

  • 先将节点p和u变成黑色,再将g变成红色,同时将g更新成cur,并且将g,p,u也做相应的更新。
  • 再将新的p和u变成合适,新的g变成红色,同时将g再更新为cur。

根据上面规律,来看抽象图:

cur是更新后待调整的节点,a/b/c/d/e是红黑树,且黑色节点个数相同。

调整过程:

  • 先将节点p和u变成红色。
  • 再将g变成黑色。
  • 更新亲戚关系

调整过后就完全符合红黑树的性质。

来看代码实现:

  • 当父亲节点是红色时,和cur的红色违背了不能有两个连续红色节点的性质,所以需要更新,如果父亲节点是黑色则不用更新。
  • 需要调整节点所在的子树是祖父节点的左子树。
  • 颜色调整完毕后,需要更新cur和parent节点,方便继续向上调整。

cur节点是父节点的右子树也是上诉方法

🍨cur红且为左子树,p为红,g为黑,u不存在/u存在且为黑

这里本喵就不再画具象图了,直接上抽象图:

先看节点u不存在:


此时节点u不存在,cur为待调整的红色节点。

调整过程:

  • 先以节点g为轴点,进行有单旋。
  • 再将节点g变成红色,节点p变成黑色,并且成为子树的新根。

调整过后,左右子树的黑色节点个数仍然不变。

需要先进行旋转的原因:

最开始从祖父节点到其左右子树的简单路径上黑色节点的个数都是1。而父节点是必须要变成黑色的,如果直接按照第一种情况那样,将父节点变成黑色,祖父节点变成红色,那么左边路径中比右边路径中黑色节点就多一个,违背了左右简单路径黑色节点个数相同的性质。

  • p为左子树,cur为左子树,先进行右单旋,再将父节点变成黑色,祖父节点变成红色。
  • p为右子树,cur为右子树,先进行左单旋,再将父节点变成黑色,祖父节点变成红色。

至于p和cur都是右子树的图本喵就不画了。

再看节点u存在且为黑:


cur为待调整的红色节点,起初左子树黑色节点有1个,右子树黑色节点有两个。

调整过程:

  • 先以祖父节点为轴点进行右单旋。
  • 再将父节点变成黑色,祖父节点变成红色。

调整过后,左边路径黑色节点个数还是1,右边路径黑色节点个数还是2,且没有两个连续的红色节点。

同样的,必须先进行旋转,否则就会减少右边路径中的黑色节点,可以自己去画一下来验证。

根据上面示意图,可以看到叔叔节点不存在和存在且为黑,它们的调整方式是一样的,都是先旋转再变色

来看代码实现:

右单旋在AVL树的时候就实现过了, 这里直接复用就可以,这样来看,代码还是比较简单的。

🍨cur红且为右子树,p为红,g为黑,u不存在/u存在且为黑

这里本喵就不再画u不存在的抽象图了,直接画u存在且为黑的抽象图:


起初cur是红色,并且是父节点的右子树,左边简单路径只有一个黑色节点,右边简单路径有两个黑色节点。

调整过程:

  • 先以父节点为轴点进行左单旋。
  • 再以祖父节点为轴点进行右单旋。
  • 再变色,将cur节点变成黑色,祖父节点变成红色。

经过调整过后,左边路径中的黑色节点个数还是1,右边路径中的黑色节点个数也还是2。

  • 父节点是左子树,cur是右子树,先进行左右双旋,再变色,将cur变成黑色,祖父节点变成红色。
  • 父节点是右子树,cur是左子树,先进行右左双旋,再变色,将cur变成黑色,祖父节点变成红色。

来看代码实现:


同样,左旋和右旋直接复用在AVL中的就行,只需要进行变色,让cur为黑,祖父节点为红。

注意: 这种情况和第二种情况都是属于先旋转再变色的,而且在变色后并不用继续更新亲戚关系,直接break跳出循环即可。

  • 第一种情况下,调整过后,子树的根节点是红色,有可能再次违背不能有两个连续红色节点的性质。
  • 第二和三种情况下,调整过后,子树的根节点是黑色,不会违背不能有两个连续红色节点的性质。

调整过程的总结:


这样一看分类非常多,但其实就是三大类。

  • uncle存在且为黑和uncle不存在的操作是一样的。
  • 是左子树还是右子树,只是旋转方向不同。

红黑树的调整,关键在于叔叔节点

//调整新节点
		//parent是红色且存在,需要调整
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			//parent都是左子节点
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				//cur是红,p是红,u存在且为红,g为黑
				if (uncle && uncle->_col == RED)
				{
					//颜色调整
					parent->_col = uncle->_col = BLACK;//父亲和叔叔节点变成黑
					grandfather->_col = RED;//祖父节点变成红

					//更新亲戚关系
					cur = grandfather;
					parent = cur->_parent;
				}
				//cur是红色,p为红色,u不存在/u存在且为黑色,g为黑色
				else
				{
					//cur是左子树
					if (cur == parent->_left)
					{
						//先进行右单旋
						RotateR(grandfather);
						//变色
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					//cur是右子树
					else
					{
						//先进左右双旋
						RotateL(parent);
						RotateR(grandfather);
						//变色
						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;//待旋转的调整完毕后,子树根节点是黑色
				}
			}
			//parent是右子节点
			else
			{
				Node* uncle = grandfather->_left;
				//情况一:叔叔存在且为红
				if (uncle && uncle->_col == RED)
				{
					//父亲和叔叔节点变成黑,祖父节点变成红
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					//更新亲戚关系
					cur = grandfather;
					parent = cur->_parent;
				}
				//叔叔不存在或者存在且为黑
				else
				{
					//情况二:cur是右子树
					if (cur == parent->_right)
					{
						//先进行左单旋
						RotateL(grandfather);
						//变色
						grandfather->_col = RED;
						parent->_col = BLACK;
					}
					//情况三:cur是左子树
					else
					{
						//先进行右单旋
						RotateR(parent);
						//再进行左单旋
						RotateL(grandfather);
						//变色
						grandfather->_col = RED;
						cur->_col = BLACK;
					}
					//旋转调整后,子树根节点是黑色,不用再调整
					break;
				}
			}
		}

整个调整部分的代码。

调整过后:

在最后返回之前,要让根节点的颜色为黑色。

🍧红黑树的验证

和AVL树一样,也需要验证一下我们的红黑树写的是否正确。同样需要写专门验证的函数,不能只看插入的结果,这样的话只能验证二叉搜索树是对的。

写一个验证红黑树的函数:


检测一棵树是否是红黑树,就要根据它的那几点性质来检测。

  • 如果是空树,直接返回真,它就是红黑树。
  • 如果根节点不是黑的,那它必然不是红黑树,如果是黑的则不一定。
  • 将最左路径中的黑色节点个数求出来,和每条路径进行比较,只要有不相等的,那么它必然不是红黑树。

比较整颗树不同简单路径中黑色节点的个数,以及判断是否有两个连续的红色节点,使用递归更加方便。

  • 在递归到每条简单路径的根节点时,比较一下该路径和最左路径中黑色节点的个数,如果不相等则它必然不是红黑树。
  • 要判断当前节点和其父节点是否是连续的红色节点,若判断当前节点和其子节点,则会很复杂。

这个参数能使用引用吗?前面经常在递归时候使用引用,但是这里不可以。

  • 当递归到某一层的时候,blackNum会在继续递归的时候传下去,在递归到后面层中改变blackNum,并不会影响到这一层的blackNum。
  • 如果这一层只有左子树,没有右子树,先递归的左子树,在递归左子树的过程中会改变blackNum,当递归回到这一层继续递归右子树的时候,需要的blackNum是这一层的blackNum,而不是从左子树返回了的blackNum。
  • 如果使用引用类型,那么在递归的过程中,blackNum会越来越大,就导致判断出错。

有兴趣的小伙伴可以画一下递归展开图就都明白了。


如上图中代码所示,我们自己创造的三个测试用例,通过插入的方式形成的树都是红黑树,验证结果返回了真。

和AVL树一样,这样测试不具有一般性,下面用随机数来测试:

void TestRBTree2()
{
	//生成随机数种子
	srand((unsigned int)time(nullptr));
	const size_t N = 10000;
	RBTree<int, int> t;
	for (size_t i = 0; i < N; i++)
	{
		size_t value = rand();
		t.insert(make_pair(value, value));
		//cout << value << ":" << value << endl;
		//t.IsRBTree();
	}
	cout << t.IsRBTree() << endl;
}


运行多次,可以看到,结果都是1,也就是说这10000个随机数构成的二叉搜索树是红黑树。说明我们之前写的插入是正确的。

和AVL树一样,红黑树的删除我们也不实现,因为比较复杂,当删除一个数据以后,同样需要进行颜色改变,最差时会从叶子更新到根,效率比较低,有兴趣的小伙伴可以自己去了解一下。

🍧红黑树与AVL树的比较

  • 红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是O( l o g 2 N log_2 N log2N)。
  • 红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数
  • 所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

红黑树的应用:

  • C++STL库–map/set、multimap/multiset。
  • linux内核
  • 其他一些库

🍧总结

红黑树比较抽象,它并不像AVL树那样直接维护平衡因子,而是通过维护红黑树的几个性质来间接维护最长路径不超过最短路径的二倍这个原则。现在我们已经对红黑树有了清晰的认识,下面就可以模拟实现map和set了,因为这两个容器的底层就是红黑树。

有关【数据结构】红黑树的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  4. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  5. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  6. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  7. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  8. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

  9. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  10. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

    我正在尝试在Rails上安装ruby​​,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf

随机推荐