[TOC]
### **什么是红黑树**
*****
红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。
通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
`红黑树`,是一棵二叉查找树,满足二叉查找树的一般性质。
` `
**红黑树的5个性质**
1. **每个结点要么是红的要么是黑的。**
2. **根结点是黑的。**
3. **每个叶节点(nil节点, 空节点)是黑色的。 注意: 这里的叶子节点是`nil叶子`**
4. **每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)**
5. **从任一节点到其每个`叶子`的所有路径都包含相同数目的黑色节点**。
>注意:
性质 3 中指定红黑树的每个叶子节点都是空节点,而且并叶子节点都是黑色。但 Java 实现的红黑树将使用 null 来代表空节点,因此遍历红黑树时将看不到黑色的叶子节点,反而看到每个叶子节点都是红色的。
` `
性质 4 的意思是:从每个根到节点的路径上不会有两个连续的红色节点,但黑色节点是可以连续的。
因此若给定黑色节点的个数 N,最短路径的情况是连续的 N 个黑色,树的高度为 N - 1;最长路径的情况为节点红黑相间,树的高度为 2(N - 1) 。
` `
性质 5 是成为红黑树最主要的条件,后序的插入、删除操作都是为了遵守这个规定。
` `
![](https://ivanzz1001.github.io/records/assets/img/data_structure/ds_rb_tree.jpg)
### 红黑树的应用
红黑树的应用比较广泛,主要是用它来存储有序的数据,它的时间复杂度是O(lgn),效率非常之高。
例如,Java集合中的[TreeSet](http://www.cnblogs.com/skywang12345/p/3311268.html)和[TreeMap](http://www.cnblogs.com/skywang12345/p/3310928.html),C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。
` `
### 红黑树基本操作
#### **红黑树的左旋右旋**
*****
红黑树的基本操作是**添加**、**删除**。在对红黑树进行添加或删除之后,都会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。
旋转包括两种:**左旋**和**右旋**。下面分别对它们进行介绍。
` `
![UTOOLS1585973639660.png](https://user-gold-cdn.xitu.io/2020/4/4/171436541173c97a?w=550&h=301&f=png&s=28644)
对x进行左旋,意味着"将x变成一个左节点"。
##### **左旋**
左旋的伪代码《算法导论》:参考上面的示意图和下面的伪代码,理解“红黑树T的节点x进行左旋”是如何进行的:
```
LEFT-ROTATE(T, x)
y ← right[x] // 前提:这里假设x的右孩子为y。下面开始正式操作
right[x] ← left[y] // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子
p[left[y]] ← x // 将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为x
p[y] ← p[x] // 将 “x的父亲” 设为 “y的父亲”
if p[x] = nil[T]
then root[T] ← y // 情况1:如果 “x的父亲” 是空节点,则将y设为根节点
else if x = left[p[x]]
then left[p[x]] ← y // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
else right[p[x]] ← y // 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子”
left[y] ← x // 将 “x” 设为 “y的左孩子”
p[x] ← y // 将 “x的父节点” 设为 “y”
```
理解左旋之后,看看下面一个更鲜明的例子。你可以先不看右边的结果,自己尝试一下。
![UTOOLS1585983105259.png](https://user-gold-cdn.xitu.io/2020/4/4/17143f5af695b125?w=564&h=376&f=png&s=121990)
` `
##### **右旋**
![UTOOLS1585974061956.png](https://user-gold-cdn.xitu.io/2020/4/4/171436bad12d13ea?w=550&h=301&f=png&s=28179)
对x进行左旋,意味着"将x变成一个左节点"。
` `
右旋的伪代码《算法导论》:参考上面的示意图和下面的伪代码,理解“红黑树T的节点y进行右旋”是如何进行的。
```
RIGHT-ROTATE(T, y)
x ← left[y] // 前提:这里假设y的左孩子为x。下面开始正式操作
left[y] ← right[x] // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子
p[right[x]] ← y // 将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为y
p[x] ← p[y] // 将 “y的父亲” 设为 “x的父亲”
if p[y] = nil[T]
then root[T] ← x // 情况1:如果 “y的父亲” 是空节点,则将x设为根节点
else if y = right[p[y]]
then right[p[y]] ← x // 情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的左孩子”
else left[p[y]] ← x // 情况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子”
right[x] ← y // 将 “y” 设为 “x的右孩子”
p[y] ← x // 将 “y的父节点” 设为 “x”
```
理解右旋之后,看看下面一个更鲜明的例子。你可以先不看右边的结果,自己尝试一下:
![UTOOLS1585983184478.png](https://user-gold-cdn.xitu.io/2020/4/4/17143f6e00a6a2a1?w=571&h=383&f=png&s=123607)
**旋转总结**:
(01) 左旋 和 右旋 是相对的两个概念,原理类似。理解一个也就理解了另一个。
(02) 下面谈谈如何区分 左旋 和 右旋。
在实际应用中,若没有彻底理解 左旋 和 右旋,可能会将它们混淆。下面谈谈我对如何区分 左旋 和 右旋 的理解。
` `
##### **区分 左旋 和 右旋**
仔细观察上面"左旋"和"右旋"的示意图。我们能清晰的发现,它们是对称的。无论是左旋还是右旋,被旋转的树,在旋转前是二叉查找树,并且旋转之后仍然是一颗二叉查找树。
![UTOOLS1585974420465.png](https://user-gold-cdn.xitu.io/2020/4/4/1714371297c4dd3e?w=550&h=301&f=png&s=38045)
**左旋示例图**(以x为节点进行左旋):
```
z
x /
/ \ --(左旋)--> x
y z /
y
```
对x进行左旋,意味着,将“x的右孩子”设为“x的父亲节点”;即,将 x变成了一个左节点(x成了为z的左孩子)!。 因此,**左旋中的“左”,意味着“被旋转的节点将变成一个左节点”**。
` `
**右旋示例图**(以x为节点进行右旋):
```
y
x \
/ \ --(右旋)--> x
y z \
z
```
对x进行右旋,意味着,将“x的左孩子”设为“x的父亲节点”;即,将 x变成了一个右节点(x成了为y的右孩子)! 因此,**右旋中的“右”,意味着“被旋转的节点将变成一个右节点”**。
` `
#### 红黑树基本操作-添加
将一个节点插入到红黑树中,需要执行哪些步骤呢?
**第一步: 将红黑树当作一颗二叉查找树,将节点插入。**
```
红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。
也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。
这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。
```
**第二步:将插入的节点着色为"红色"。**
将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。
**第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。**
```
第二步中,将插入节点着色为"红色"之后,不会违背"特性(5)"。那它到底会违背哪些特性呢?
对于"特性(1)",显然不会违背了。因为我们已经将它涂成红色了。
对于"特性(2)",显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。
对于"特性(3)",显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。
对于"特性(4)",是有可能违背的!
那接下来,想办法使之"满足特性(4)",就可以将树重新构造成红黑树了。
```
添加操作的伪代码《算法导论》:
```
RB-INSERT(T, z)
y ← nil[T] // 新建节点“y”,将y设为空节点。
x ← root[T] // 设“红黑树T”的根节点为“x”
while x ≠ nil[T] // 找出要插入的节点“z”在二叉树T中的位置“y”
do y ← x
if key[z] < key[x]
then x ← left[x]
else x ← right[x]
p[z] ← y // 设置 “z的父亲” 为 “y”
if y = nil[T]
then root[T] ← z // 情况1:若y是空节点,则将z设为根
else if key[z] < key[y]
then left[y] ← z // 情况2:若“z所包含的值” < “y所包含的值”,则将z设为“y的左孩子”
else right[y] ← z // 情况3:(“z所包含的值” >= “y所包含的值”)将z设为“y的右孩子”
left[z] ← nil[T] // z的左孩子设为空
right[z] ← nil[T] // z的右孩子设为空。至此,已经完成将“节点z插入到二叉树”中了。
color[z] ← RED // 将z着色为“红色”
RB-INSERT-FIXUP(T, z) // 通过RB-INSERT-FIXUP对红黑树的节点进行颜色修改以及旋转,让树T仍然是一颗红黑树
```
结合伪代码以及为代码上面的说明,先理解RB-INSERT。理解了RB-INSERT之后,我们接着对 RB-INSERT-FIXUP的伪代码进行说明。
添加修正操作的伪代码《算法导论》
```
RB-INSERT-FIXUP(T, z)
while color[p[z]] = RED // 若“当前节点(z)的父节点是红色”,则进行以下处理。
do if p[z] = left[p[p[z]]] // 若“z的父节点”是“z的祖父节点的左孩子”,则进行以下处理。
then y ← right[p[p[z]]] // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)”
if color[y] = RED // Case 1条件:叔叔是红色
then color[p[z]] ← BLACK ▹ Case 1 // (01) 将“父节点”设为黑色。
color[y] ← BLACK ▹ Case 1 // (02) 将“叔叔节点”设为黑色。
color[p[p[z]]] ← RED ▹ Case 1 // (03) 将“祖父节点”设为“红色”。
z ← p[p[z]] ▹ Case 1 // (04) 将“祖父节点”设为“当前节点”(红色节点)
else if z = right[p[z]] // Case 2条件:叔叔是黑色,且当前节点是右孩子
then z ← p[z] ▹ Case 2 // (01) 将“父节点”作为“新的当前节点”。
LEFT-ROTATE(T, z) ▹ Case 2 // (02) 以“新的当前节点”为支点进行左旋。
color[p[z]] ← BLACK ▹ Case 3 // Case 3条件:叔叔是黑色,且当前节点是左孩子。(01) 将“父节点”设为“黑色”。
color[p[p[z]]] ← RED ▹ Case 3 // (02) 将“祖父节点”设为“红色”。
RIGHT-ROTATE(T, p[p[z]]) ▹ Case 3 // (03) 以“祖父节点”为支点进行右旋。
else (same as then clause with "right" and "left" exchanged) // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
color[root[T]] ← BLACK
```
` `
根据被插入节点的父节点的情况,可以将"当节点z被着色为红色节点,并插入二叉树"划分为**三种情况来处理**。
① 情况说明:**被插入的节点是根节点**。
处理方法:直接把此节点涂为黑色。
② 情况说明:**被插入的节点的父节点是黑色**。
处理方法:什么也不需要做。节点被插入后,仍然是红黑树。
③ 情况说明:**被插入的节点的父节点是红色**。
处理方法:那么,该情况与红黑树的“特性(5)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为3种情况(Case)。
![UTOOLS1585975414625.png](https://user-gold-cdn.xitu.io/2020/4/4/171438051d7ae558?w=1195&h=366&f=png&s=79901)
3.1 **被插入的节点的父节点是红色+叔叔节点也是红色**
* **处理策略**
(01) 将“父节点”设为黑色。
(02) 将“叔叔节点”设为黑色。
(03) 将“祖父节点”设为“红色”。
(04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。
![UTOOLS1585984352665.png](https://user-gold-cdn.xitu.io/2020/4/4/1714408bf8b0f823?w=1608&h=522&f=png&s=116909)
3.2 **被插入的节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子**
* **处理策略**
(01) 将“父节点”作为“新的当前节点”。
(02) 以“新的当前节点”为支点进行左旋。
` `
变化前\[当前节点为7节点\]:
![UTOOLS1585984728097.png](https://user-gold-cdn.xitu.io/2020/4/4/171440e6d9c9b0f3?w=424&h=406&f=png&s=36072)
变化后:
![UTOOLS1585985311545.png](https://user-gold-cdn.xitu.io/2020/4/4/171441756013df46?w=496&h=406&f=png&s=36661)
3.3 **被插入的节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子**
* **处理策略**
(01) 将“父节点”设为“黑色”。
(02) 将“祖父节点”设为“红色”。
(03) 以“祖父节点”为支点进行右旋。
` `
变化前\[当前节点为2节点\]:
![UTOOLS1585986165095.png](https://user-gold-cdn.xitu.io/2020/4/4/17144246ff6a21f5?w=496&h=406&f=png&s=36661)
变化后:
![UTOOLS1585986235278.png](https://user-gold-cdn.xitu.io/2020/4/4/171442575cbad917?w=568&h=334&f=png&s=33561)
` `
#### **红黑树基本操作-删除**
将红黑树内的某一个节点删除。需要执行的操作依次是:首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;然后,通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。详细描述如下:
**第一步:将红黑树当作一颗二叉查找树,将节点删除。**
这和"删除常规二叉查找树中删除节点的方法是一样的"。分3种情况:
① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。
② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的情况了,下面就考虑后继节点。 在"被删除节点"有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然"的后继节点"不可能双子都非空,就意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"情况① "进行处理;若只有一个儿子,则按"情况② "进行处理。
**第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。**
因为"第一步"中删除节点之后,可能会违背红黑树的特性。所以需要通过"旋转和重新着色"来修正该树,使之重新成为一棵红黑树。
` `
删除操作的伪代码《算法导论》:
```
RB-DELETE(T, z)
if left[z] = nil[T] or right[z] = nil[T]
then y ← z // 若“z的左孩子” 或 “z的右孩子”为空,则将“z”赋值给 “y”;
else y ← TREE-SUCCESSOR(z) // 否则,将“z的后继节点”赋值给 “y”。
if left[y] ≠ nil[T]
then x ← left[y] // 若“y的左孩子” 不为空,则将“y的左孩子” 赋值给 “x”;
else x ← right[y] // 否则,“y的右孩子” 赋值给 “x”。
p[x] ← p[y] // 将“y的父节点” 设置为 “x的父节点”
if p[y] = nil[T]
then root[T] ← x // 情况1:若“y的父节点” 为空,则设置“x” 为 “根节点”。
else if y = left[p[y]]
then left[p[y]] ← x // 情况2:若“y是它父节点的左孩子”,则设置“x” 为 “y的父节点的左孩子”
else right[p[y]] ← x // 情况3:若“y是它父节点的右孩子”,则设置“x” 为 “y的父节点的右孩子”
if y ≠ z
then key[z] ← key[y] // 若“y的值” 赋值给 “z”。注意:这里只拷贝z的值给y,而没有拷贝z的颜色!!!
copy y's satellite data into z
if color[y] = BLACK
then RB-DELETE-FIXUP(T, x) // 若“y为黑节点”,则调用
return y
```
结合伪代码以及为代码上面的说明,先理解RB-DELETE。理解了RB-DELETE之后,接着对 RB-DELETE-FIXUP的伪代码进行说明:
```
RB-DELETE-FIXUP(T, x)
while x ≠ root[T] and color[x] = BLACK
do if x = left[p[x]]
then w ← right[p[x]] // 若 “x”是“它父节点的左孩子”,则设置 “w”为“x的叔叔”(即x为它父节点的右孩子)
if color[w] = RED // Case 1: x是“黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。
then color[w] ← BLACK ▹ Case 1 // (01) 将x的兄弟节点设为“黑色”。
color[p[x]] ← RED ▹ Case 1 // (02) 将x的父节点设为“红色”。
LEFT-ROTATE(T, p[x]) ▹ Case 1 // (03) 对x的父节点进行左旋。
w ← right[p[x]] ▹ Case 1 // (04) 左旋后,重新设置x的兄弟节点。
if color[left[w]] = BLACK and color[right[w]] = BLACK // Case 2: x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。
then color[w] ← RED ▹ Case 2 // (01) 将x的兄弟节点设为“红色”。
x ← p[x] ▹ Case 2 // (02) 设置“x的父节点”为“新的x节点”。
else if color[right[w]] = BLACK // Case 3: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。
then color[left[w]] ← BLACK ▹ Case 3 // (01) 将x兄弟节点的左孩子设为“黑色”。
color[w] ← RED ▹ Case 3 // (02) 将x兄弟节点设为“红色”。
RIGHT-ROTATE(T, w) ▹ Case 3 // (03) 对x的兄弟节点进行右旋。
w ← right[p[x]] ▹ Case 3 // (04) 右旋后,重新设置x的兄弟节点。
color[w] ← color[p[x]] ▹ Case 4 // Case 4: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的。(01) 将x父节点颜色 赋值给 x的兄弟节点。
color[p[x]] ← BLACK ▹ Case 4 // (02) 将x父节点设为“黑色”。
color[right[w]] ← BLACK ▹ Case 4 // (03) 将x兄弟节点的右子节设为“黑色”。
LEFT-ROTATE(T, p[x]) ▹ Case 4 // (04) 对x的父节点进行左旋。
x ← root[T] ▹ Case 4 // (05) 设置“x”为“根节点”。
else (same as then clause with "right" and "left" exchanged) // 若 “x”是“它父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
color[x] ← BLACK
```
` `
### **红黑树相对于哈希表,在选择使用的时候有什么依据?**
*****
权衡三个因素: 查找速度, **数据量, 内存使用,可扩展性**。
总体来说,hash查找速度会比map快,而且查找速度基本和数据量大小无关,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n) 小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash。但若你对内存使用特别严格, 希望程序尽可能少消耗内存,那么一定要小心,hash可能会让你陷入尴尬,特别是当你的hash对象特别多时,你就更无法控制了,而且 hash的构造速度较慢。
红黑树并不适应所有应用树的领域。如果数据基本上是静态的,那么让他们待在他们能够插入,并且不影响平衡的地方会具有更好的性能。如果数据完全是静态的,例如,做一个哈希表,性能可能会更好一些。
在实际的系统中,例如,需要使用动态规则的防火墙系统,使用红黑树而不是散列表被实践证明具有更好的伸缩性。Linux内核在管理vm\_area\_struct时就是采用了红黑树来维护内存块的。
红黑树通 过扩展节点域可以在不改变时间复杂度的情况下得到结点的秩。
### **红黑树时间复杂度**
*****
红黑树虽然本质上是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了**红黑树的查找、插入、删除的时间复杂度最坏为O(log n)**。
- Unity3D
- Unity3D学习路线
- U3D基础
- UGUI
- 数据结构和算法
- 算法时间复杂度
- 二叉树
- B树 & B+树
- 红黑树
- 跳跃表
- Lecod算法题目
- C++-排序算法
- sort排序
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 希尔排序
- 堆排序
- 归并排序
- 递归算法
- LSMs和B tree
- mysql引擎
- 汇编程序
- 汇编入门 Hello World
- 汇编语言整数加减法
- 寄存器的使用和说明
- 汇编语言常用知识点
- 汇编语言中的几个伪指令
- 汇编语言数据类型以及数据定义
- 汇编语言计算数组和字符串长度
- 汇编语言中寄存器加[]的意思
- 汇编语言中$符号的用法
- 汇编语言系统调用(System Calls)
- 汇编语言push和pop指令
- 汇编语言寻址操作
- 汇编语言进阶
- GNUx86-64汇编
- C/C++调用汇编函数
- 用汇编理解C函数的调用过程和返回值
- 从汇编的角度看C++
- C/C++
- C++-编程入门
- C/C++环境搭建
- JsonCPP的使用
- 连接数据库
- 连接mysql
- connector
- C API
- 连接sqlite3
- 使用sqlite3步骤
- 使用Clion
- thread-多线程
- 初识thread
- detach陷阱
- 事实
- 陷阱总结
- 剪切板操作
- 剪切板基本操作
- 剪切板详细api
- 文件操作
- 桌面右键菜单批处理
- Resource Hacker
- 获取指定输入法
- 学习网站
- C++11中的匿名函数(lambda函数,lambda表达式)
- sleep和usleep的区别
- 使用std::unique_ptr 管理 FILE 指针
- typedef的用法
- strtuct中的char*和char数组
- 各个平台不同类型占用字节数
- C++进阶
- C++浅拷贝和深拷贝的区别
- C++类型强制转换
- C++11写的定时器
- C调用java函数
- C++11 特性
- 二进制兼容
- GDB的基础命令
- GDB调试死锁
- 核心底层代码
- 线程池的实现
- 线程池的应用场景
- C++协程库
- C++定时器原理
- 通信协议
- Socket5协议
- https 协议
- TCP-拥塞控制
- C++-STL
- map/unordered_map/hash_map区别
- 初始化vector
- STL算法
- Effective STL
- 条款5:尽量使用区间成员函数代替它们的单元素兄弟
- 条款9:在删除选项中仔细选择
- 条款13:尽量使用vector和string来代替动态分配的数组
- 条款14:使用reserve来避免不必要的重新分配
- 条款16: 如何将vector和string的数据传给遗留的API
- 条款17:使用“交换技巧”来修整过剩容量
- 条款18:避免使用vector<bool>
- 条款30:确保目标区间足够大
- 编辑器
- VS Code
- 配置C++
- 命令行编译
- CMake
- CMake 升级
- cmake-基本操作
- 设置入口
- 修改vs运行时库
- CMake生成sln
- CMake设置输出目录
- CMake添加GDB调试
- 使静态库和动态库同时存在
- C/C++网络编程
- 网络基础
- 5种网络IO模型总结
- 条件变量
- 设置阻塞socket超时时间
- ccnet
- 一个reactor单线程库
- ccnet从单线程转变为多线程
- IO多路复用
- IO多路复用的理解
- EPOLL
- select示例代码
- epoll 示例代码
- iocp示例代码
- muduo库
- muduo编译
- Libevent的简单使用
- 编译libevent
- Libevent几个简单的api
- Libevent 定时器
- Libevent通用的编程技法
- Libevent简单的Server/Client
- Boost库学习
- Boost库编译
- 利用Boost 实现线程池
- boost::asio
- boost::mutex
- Boost解析Json
- Boost.Asio的一些想法
- win32t网络编程
- 简单的c/s socket通信
- 回响
- 迭代服务器跟客户端
- 进行类创建
- socket文件传输
- 简单的udp
- Reactor模型与Proactor模型
- Actor和CSP模型
- 大量的timewait
- EPOLL的bug
- C++-界面
- MFC
- mfc小知识
- MFC吕鑫
- 初识mfc
- 初始化
- 消息映射
- 组合键 与(&)运算
- WIN32+MFC自定义消息
- 对话框的相关消息
- DestroyWindow
- GDI
- 初窥
- 坐标
- 创建画笔
- CDC
- CPaintDC
- CPen
- CBursh
- CFont
- CBitmap
- LoadImage
- CMemDC
- 自适应
- 双缓冲问题
- 闪烁问题
- 小型软件开发
- 记事本
- 图形架构软件
- 提纲图形
- 操作
- 重载关闭按钮
- 自定义消息
- 自绘按钮
- 自绘基础知识
- 自绘按钮提纲
- 步骤
- 自会下拉列表
- 自绘下拉列表
- 自绘菜单栏
- MFC函数类
- SetTimer
- 高级控件应用
- 高级控件开发提纲
- 菜单栏
- 网络通信协议
- 提纲
- sizeof====strlen
- 堆 == 栈
- Socket
- 基本代码
- UDP协议
- Win32
- 窗口操作
- 创建窗口,自定义按钮
- 给按钮加背景图
- 给窗口加背景
- 贴图
- DLL组件创建
- HOOK钩子
- MinGW
- duilib
- 地址
- 属性列表
- 第一个duilib项目
- DUI自带的完整
- ListControl
- TreeView
- 重设窗口大小
- 计算DPI
- HandleMessage跟MessageHandle
- CEF
- cef环境搭建
- cefsimple简单流程
- 优化CEF
- P2P
- stun搭建
- QT5
- QT5环境安装
- QT信号与槽的概念
- QT工程CMakeLists.txt文件的编写
- QT32位
- libShadowQT
- GoflywayQT
- 计划
- Protocol Buffer
- ProtoBuf安装
- 包管理器
- vcpkg
- conan
- xmake
- C++面试总结
- 基础
- 分布式锁
- C++重载、覆盖与多态性
- 20道必须掌握道C++面试题
- 传值、传地址、传引用总结
- 50道面试题 (1)
- 50道面试题 (2)
- 内联函数的作用以及使用限制
- vector的resize用法
- 虚函数/虚表/虚基类
- 公司面试
- 面试:简单算法题目
- 面试:GetMemory
- 2021-3/11号面试记录(lihe)
- leetcode
- leetcode331-验证二叉树的前序序列化
- leetcode141. 环形链表
- C/C++程序员面试秘籍
- 链表
- 使用C/C++实现atoi和itoa函数
- mysql面试题
- 协程解析
- 协程解析一(ucontext解析)
- 协程解析二(云风的coroutine)
- 进程、线程、协程
- 自己制作一个协程库
- C语言中两个指针间的运算
- Windows中一些宏的含义
- C++书籍在线观看
- 安装TeamTalk
- Lua和C/C++互相調用
- android环境配置
- TCP/IP
- 三次握手四次挥手
- 有限状态机
- 游戏开发
- UE4
- 开发一个fps的游戏
- 环境安装,让人物跑起来
- 增加血条和护甲
- 再生盔甲和伤害功能
- 最后一战
- 最后一战安装部署
- 登录流程 LS & BS & CS
- 最后一战-游戏场景服务器SS
- 降临
- 降临安装部署
- skynet
- skynet安装部署
- lua-protobuc库--skynet使用自定义protobuf
- pbc库--skynet使用自定义protobuf
- 扫雷
- 仙剑奇侠传
- 炉石传说
- unity环境搭建
- 寻路算法
- 音视频
- WebRTC
- webrtc源码下载
- webrtc 编译
- gn和ninja文件作用
- webrtc 源码目录结构
- WebRTC实时互动入门
- web 服务
- nodejs 搭建http服务
- nodejs 搭建https服务
- webrtc 获取音视频设备
- webrtc 音视频采集
- webrtc 音视频约束
- webrtc 浏览器视频特效
- webrtc 从视频中获取图片
- webrtc 只采集音频数据
- webrtc MediaStream和获取视频约束
- webrtc 媒体流的录制
- webrtc 捕获桌面
- webrtc 信令服务器
- webrtc 传输基本知识
- webrtc NAT
- webrtc ICE
- webrtc 媒体能力协商
- webrtc 端到端链接的基本流程
- webrtc SDP
- webrtc STUN/TURN
- webrtc 客户端信令消息
- webrtc 视频通话实现
- webrtc 传输速率控制
- webrtc 统计信息
- webrtc IOS
- Kamailio
- webrtc的分析
- Webrtc音视频会议之Mesh/MCU/SFU三种架构
- RTSP / RTP / RTCP协议
- RTMP / RTSP / WebRTC之间的关系
- webrtc源码
- PeerConnection解析
- FFmpeg
- FFmpeg命令行的使用
- ffmpeg命令语法
- FFmpeg设备采集
- FFmpeg生成水印
- FFmpeg画中画和视频多宫格
- FFmpeg定时截图
- FFmpeg基本概念
- FFmpeg基本模块
- ffmpeg 滤镜处理
- ffmpeg流的指定
- FFmpeg相关api
- 基本函数
- 打印音视频信息
- 抽取音视频数据
- 捕捉摄像头并推流
- FFmpeg拉流截图
- vs2017编译错误
- 自定义跨平台FFmpeg播放器
- ffmpeg拉流并且使用qt
- ffmpeg读取摄像头并且推流
- ASS和SRT字幕有何区别
- 解决ffmpeg 在avformat_find_stream_info执行时间太长
- sws_getContext()处理AV_PIX_FMT_NONE 帧格式引起的core dump
- OWT系列
- owt-server
- owt-server 编译运行
- owt-server模块
- owt-client-javascript解析
- owt-client-android
- owt-android编译运行
- owt-client-android系列分析
- owt-conference
- Licode
- licode安装
- licode 系列
- basic example client
- basic example server
- 音视频基础概念
- 视频播放中的码率的概念
- 帧率
- nginx-rtmp 模块搭建与使用
- RTMP分析
- RTMP规范
- RTMP流媒体播放过程
- 一段简单的CMakeLists.txt
- Go
- Go Base
- Go 环境安装
- mod
- Go 流程控制
- interface convert to string/int/float64
- Go mod拉取私有仓库
- VSCode配置go环境
- Go 设置代理
- Viper读取配置文件
- vim打造成go的ide
- Go 交叉编译
- GO 简单功能
- Golang发起http请求
- Go 定时任务
- websocket协议
- Golang的定时器
- JWT认证
- Google Protobuf 请求参数为空的案例
- Go文件下载
- Go 服务热更新方案
- Go 静态服务器
- gocolly的使用
- golang中获取字符串长度的几种方法
- hugo搭建静态博客
- go利用reids实现分布式锁
- Go 代理
- Go 简单http代理
- Go SS代理流程
- Go AES加密和解密的三种模式实现(CBC/ECB/CFB)
- Go 负载均衡
- Go 标准库
- reflect.Type和reflect.Value
- container & list & ring & heap
- Context
- http 请求
- Go base64
- Go struct <=> json
- Go切片合并
- Go 包的使用
- pprof包的使用
- Go Grpc
- ymal 配置文件
- 日志包 logrus / zap
- Go 命令行多指令操作
- Cobra/viper 命令行解析
- Go sync/atomic
- zap日志
- Go 进阶
- Go sync.Mutex详解
- 使用自定义头和protobuf解决沾包问题
- 使用 build tag 来自定义构建配置
- 使用valgrind检测程序是否内存泄露
- Go参数传递是值传递还是引用传递
- Go 切片/数组
- Channel的使用
- Go Interface详解
- GO-IM系统
- IM架构
- Go搭建一个http服务器
- mattermost-server
- matter编译部署
- mattermost配置
- matter详解
- Goim
- Centrifugo
- Tinode
- cgo入门
- GO语言中使用C语言
- reflect.StringHeader和reflect.SliceHeader
- Cgo使用libevent库实现一个定时器
- cgo遍历C结构体数组
- Go和C之间的类型转换
- Elasticsearch
- Elasticsearch安装
- etcd的使用
- etcd 安装
- Docker
- Docker 安装部署
- 修改Docker镜像源
- 使用Dockerfile构建部署项目
- 使用Dockerfile多阶段构建
- Dockerfile指令解析
- Volume
- 创建一个images
- Docker容器管理
- Shipyard
- Portainer
- lazydocker-docker 终端ui管理
- Docker 容器-ssh登录
- Dockerfile CMD启动命令
- Docker 容器独立ip
- 清理 Docker文件
- Docker-Composer
- Docker远程访问
- Docker 远程访问API设置
- Docker 结合IDEA使用
- Docker 使用错误
- Docker镜像瘦身
- Docker查看退出码 exitCode
- Docker安装宝塔
- Docker创建calibre-web
- Docker不能使用gdb调试的解决方案
- k8s
- K8s安装部署
- 安装部署coreDNS
- web管理之一 Dashboard
- dashboard的yaml文件
- 集群监控 heapster
- 资源监控 metrics
- web管理之二 Prometheus
- idea k8s插件
- 第一个 k8s应用
- k8s将pod在master上运行
- k8s网络通信模型
- Deployment和Pod区别
- Statefulset的基本使用
- k8s的持久化存储 PersistentVolume
- Ingress基本用法
- k8s错误处理
- 角色权限
- busybox k8s的调试工具
- nfs的安装和使用
- Kafka
- kafka介绍
- Redis
- Redis的安装
- Redis主从配置
- Redis数据类型
- Redis-Set
- Redis-Hash
- Redis设计与实现
- 第一节:sds
- 第二节:链表的实现
- 第三节:字典的实现(一) - 基本原理
- 第四节:字典的实现(二) - 哈希算法
- 第五节:字典的实现(三) - 哈希冲突解决方案
- 第六节:字典的实现(四) - rehash原理
- 第七节:跳跃表
- 第七节:整数集合
- 第八节:压缩列表
- 第九节:对象
- 总结
- Redis源码分析
- 配置VScode调试Redis源码
- VScode调试Redis源码,指针显示的问题
- Redis模块概述
- Redis的五个数据类型
- sds字符串分析
- adlist分析
- ziplist压缩列表
- quicklist
- dict字典--hashtable
- zskiplist-跳跃表
- sparkline微线图
- Redis源码的一些基础知识总结
- 在redis中遇见redisObject struct
- acl库编写Redis客户端
- hireids操作
- 当内存耗尽时,redis怎么做
- 如何保证redis的高并发及高可用?
- 使用redis实现分布式锁
- Redis管道技术测试
- MongoDB
- MongoDB安装
- MongoDB免安装版
- Mongodb C Driver驱动安装
- MongoDB知识点
- MongoDB基础
- MongodB原子操作
- MongoDB索引
- MongoDB主从/副本集
- MongoDB分片集群
- MongoDB性能检测
- MongoDB构建模式
- Mongo-cxx-driver
- mongo-c-driver
- MongoDB用户操作
- MySQL
- MySQL安装
- 一个机器多个MySQL
- 创建远程链接
- 字段编辑
- 存储过程
- MySQL严格模式
- Mysql 丢失Root密码
- 中国全省市表
- 高性能MySQL
- MySQL并发控制
- MySQL基准测试
- MySQL服务器性能剖析
- MySQLSchema与数据类型优化
- MySQL创建高性能索引
- MySQL复制
- MySQL-高可用
- MySQL引擎
- DB
- Oracle
- ORACLE9i安装
- Oracle存储过程
- Oracle 存储过程基础组件
- Oracle存储过程示例
- Other Language
- Python
- python编程通用概念
- python安装
- pycharm-docker调试
- Python安装AES加密
- python安装pip
- 错误
- py框架
- Django
- 开始一个项目
- 路由
- 模型层
- 创建博客文章模型
- Django Shell
- 初识Django Admin模块
- 实现博客数据返回页面
- 初始Django视图与模板
- boot静态页面
- django分页
- Django设置
- djangocms
- 语言特性
- 切片
- PHP
- php外部扩展
- 添加C扩展
- 添加外部C扩展
- 添加redis
- redis
- 下载
- 封装
- 外部访问配置
- redis基本操作
- 框架
- TP5
- Model
- 自动写入时间戳
- Laravel
- 安装
- TP3.2
- CACHE缓存
- create
- curl
- 文件下载
- 模块名字
- 常用工具
- 功能代码
- 检测磁盘剩余空间
- 静态类
- 消除html标签
- 检测手机号
- 毫秒 == 日期格式
- jQuery
- 找子元素
- php网络编程
- socket
- socket_server.php
- socket_client.php
- websocket
- websocket_server.php
- websocket_client.html
- websocket_unit.js
- swoole
- 环境依赖及安装
- 搭环境
- windows搭建apache+php7
- nginx做成服务顺便配置php
- Lua
- Lua环境安装
- lua api
- lua_pop & lua_settop
- lua_next
- JAVA
- Java通用编程概念
- Java环境安装
- 编译遇到的问题
- 请求接口
- java变量类型
- Android
- IDEA 配置 gradle
- Rust
- Rust编程通用概念
- Rust安装
- 更换crates源
- 写一个hello world
- 变量可变性
- 数据类型
- Struct+方法语法
- 赋值
- tokio网络框架
- tokio安装
- EchoServer
- 实现Future
- 组合器
- shadowsocket-rust
- shadowsocket-rust安装
- Scheme
- 环境搭建及基本语法
- JavaScript
- NodeJs
- React
- React-Native
- 使用pkg打包
- Nginx
- Nginx-反向代理
- OpenResty初探
- OpenResty做一个postman
- lua没有continue
- nginx 配置静态服务器
- 将luarocks整合进openresty,并安装lfs
- Git
- GitHub基本操作
- Github跟本地的配置和操作
- GitHub搜索
- Github镜像
- git修改远程仓库
- Git基本操作
- 安装gitlab
- VC工程的.gitignore
- Git 设置代理
- Git克隆部分文件
- Linux
- 用户操作
- 防火墙操作
- 压缩
- Linux时间同步
- CURL
- Linux samba文件共享
- 使用cat创建新文件并追加内容
- htop / glances / dstat
- IPC错误
- nc的使用
- 核与线程 CPU 4核8线程 的解释
- Linux 使用 MLDonkey 下载 ed2k
- Linux技巧
- LINUX技巧-查找文件行中值重复的行
- tcpdump 抓包
- 日志查找
- nethogs 查看网络流量
- 系统中加入库目录
- 将root权限的文件改为用户权限
- linux 打开文件数 too many open files 解决方法
- 查看系统CPU/GPU/磁盘io
- 快速删除大量文件的方法
- Linux-文件传输
- 安装 nvidia 驱动
- 改造VIM
- 通过vimplus项目一键配置vim
- 自定义vim配置C++IDE
- 终端配色
- VIM+项目管理
- vimplus快捷键
- 自动切换输入法
- Shell编程
- shell脚本守护进程
- if [ $# -eq 0 ]该语句是什么含义?
- 从命令行提示输入,和自动输入,自动交互
- grep指令
- cut指令
- awk指令
- xargs
- 使用except自动交互
- Ubuntu
- 界面安装
- 更换源
- Ubuntu安装docker
- Ubuntu18 安装qt
- 更新密钥
- Ubuntu开启远程登录
- Ubuntu16.04界面无法启动
- apt-get install 没有自动安装
- dpkg: 处理软件包 nginx (--configure)时出错
- ubuntu下浏览器使用代理
- Ubuntu把放大缩小按钮移动到左边
- wine 安装错误
- Ubuntu下安装Microsoft to do
- 在Ubuntu上使用ssh连接另外一台机器出问题
- 解决windows和ubuntu16.04虚拟机拖放问题
- 解决apt-get /var/lib/dpkg/lock-frontend 问题
- Ubuntu安装cinnamon
- sudo apt-get update错误
- googlechrome
- Ubuntu16.04安装xmind
- Ubuntu下载迅雷
- Linux护眼宝
- 查看Ubuntu安装的界面
- 使用aria2
- CentOS7使用yum安装gcc
- System
- MAC
- 安装软件
- mac基本操作
- 安装pod
- 改造终端
- VIM配置
- Chroom浏览器https访问
- mac摄像头打不开
- Mac与Windows或Linux的键鼠共享神器Synergy
- Windows
- 小工具
- bat文件的使用
- bat把exe文件做成单击右键可运行的
- copy
- 注册 dll
- 镜像==分区
- choco
- BaiduPCS-go
- tail日志查看命令
- 右键菜单没有选项
- Proxy SwitchyOmega
- Google云服务器配置
- 百度网盘不限速
- 远程桌面
- 百度地图离线开发
- 查看端口
- SC命令使用
- 开发
- TIME_WAIT过多导致服务不能被访问
- 修改win的默认编码
- 百度网盘二维码刷新不出来
- 移动端
- Object-C
- 录音跟播放
- 视频的采集跟播放
- Swift
- Swift编程通用概念
- Switf环境安装
- Swift Package Manager(SPM)
- 手动导入库
- PerfectTemplate的使用
- PerfectTemplate环境搭建
- ios直播开发
- Simple-RTMP-Server
- Mac上安装ffmpeg环境
- 推流拉流
- 仿直播app开发
- 框架搭建
- 开发流程
- React-Native
- React-native环境安装
- 分布式追踪系统
- Jaeger 客户端库
- LightStep 的使用
- 软件
- PhpStorm
- 安装ThinkStrom
- 添加xdebug
- Clion
- C++开发配置
- 激活码
- 在linux上制作桌面图标
- Vagrant
- VMWare
- VirtualBox
- proxifier + Shadowshocks
- Cmder
- Navicate For MongoDB
- MinDoc
- GitHub速度慢
- 科学
- VMware虚拟机磁盘操作占用过高问题
- PhotoShop+Premiere下载
- ActionView安装部署
- 读书笔记
- 博客
- hexo
- 部署
- jekyll
- 在线编译器
- 书屋
- 如何阅读一本书
- 个人发展
- Linux高性能服务器读书笔记
- TCP/IP协议族
- IP协议
- TCP协议详解
- TCP协议的拥塞控制
- 安全测试
- 常见web安全漏洞
- 程序设计
- log日志设计
- 爬虫项目
- Python3.7的安装
- Scrapy的安装和使用
- Colly框架
- Crawlab是一款款里爬虫的web框架
- 英文学习