[TOC]
### **什么是紅黑樹**
*****
紅黑樹,一種二叉查找樹,但在每個結點上增加一個存儲位表示結點的顏色,可以是Red或Black。
通過對任何一條從根到葉子的路徑上各個結點著色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長出倆倍,因而是接近平衡的。
`紅黑樹`,是一棵二叉查找樹,滿足二叉查找樹的一般性質。
` `
**紅黑樹的5個性質**
1. **每個結點要么是紅的要么是黑的。** ?
2. **根結點是黑的。** ?
3. **每個葉節點(nil節點, 空節點)是黑色的。 注意: 這里的葉子節點是`nil葉子`** ?
4. **每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)**
5. ?**從任一節點到其每個`葉子`的所有路徑都包含相同數目的黑色節點**。
>注意:
性質 3 中指定紅黑樹的每個葉子節點都是空節點,而且并葉子節點都是黑色。但 Java 實現的紅黑樹將使用 null 來代表空節點,因此遍歷紅黑樹時將看不到黑色的葉子節點,反而看到每個葉子節點都是紅色的。
` `
性質 4 的意思是:從每個根到節點的路徑上不會有兩個連續的紅色節點,但黑色節點是可以連續的。
因此若給定黑色節點的個數 N,最短路徑的情況是連續的 N 個黑色,樹的高度為 N - 1;最長路徑的情況為節點紅黑相間,樹的高度為 2(N - 1) 。
` `
性質 5 是成為紅黑樹最主要的條件,后序的插入、刪除操作都是為了遵守這個規定。
` `

### 紅黑樹的應用
紅黑樹的應用比較廣泛,主要是用它來存儲有序的數據,它的時間復雜度是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條性質,也就不再是一顆紅黑樹了,而是一顆普通的樹。而通過旋轉,可以使這顆樹重新成為紅黑樹。簡單點說,旋轉的目的是讓樹保持紅黑樹的特性。
旋轉包括兩種:**左旋**和**右旋**。下面分別對它們進行介紹。
` `

對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”
```
理解左旋之后,看看下面一個更鮮明的例子。你可以先不看右邊的結果,自己嘗試一下。

` `
##### **右旋**

對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”
```
理解右旋之后,看看下面一個更鮮明的例子。你可以先不看右邊的結果,自己嘗試一下:

**旋轉總結**:
(01) 左旋 和 右旋 是相對的兩個概念,原理類似。理解一個也就理解了另一個。
(02) 下面談談如何區分 左旋 和 右旋。
在實際應用中,若沒有徹底理解 左旋 和 右旋,可能會將它們混淆。下面談談我對如何區分 左旋 和 右旋 的理解。
` `
##### **區分 左旋 和 右旋**
仔細觀察上面"左旋"和"右旋"的示意圖。我們能清晰的發現,它們是對稱的。無論是左旋還是右旋,被旋轉的樹,在旋轉前是二叉查找樹,并且旋轉之后仍然是一顆二叉查找樹。

**左旋示例圖**(以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)。

3.1 **被插入的節點的父節點是紅色+叔叔節點也是紅色**
* **處理策略**
(01) 將“父節點”設為黑色。
(02) 將“叔叔節點”設為黑色。
(03) 將“祖父節點”設為“紅色”。
(04) 將“祖父節點”設為“當前節點”(紅色節點);即,之后繼續對“當前節點”進行操作。

3.2 **被插入的節點的父節點是紅色,叔叔節點是黑色,且當前節點是其父節點的右孩子**
* **處理策略**
(01) 將“父節點”作為“新的當前節點”。
(02) 以“新的當前節點”為支點進行左旋。
` `
變化前\[當前節點為7節點\]:

變化后:

3.3 **被插入的節點的父節點是紅色,叔叔節點是黑色,且當前節點是其父節點的左孩子**
* **處理策略**
(01) 將“父節點”設為“黑色”。
(02) 將“祖父節點”設為“紅色”。
(03) 以“祖父節點”為支點進行右旋。
` `
變化前\[當前節點為2節點\]:

變化后:

` `
#### **紅黑樹基本操作-刪除**
將紅黑樹內的某一個節點刪除。需要執行的操作依次是:首先,將紅黑樹當作一顆二叉查找樹,將該節點從二叉查找樹中刪除;然后,通過"旋轉和重新著色"等一系列來修正該樹,使之重新成為一棵紅黑樹。詳細描述如下:
**第一步:將紅黑樹當作一顆二叉查找樹,將節點刪除。**
? ? ? ?這和"刪除常規二叉查找樹中刪除節點的方法是一樣的"。分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框架
- 英文學習