Skip to content

Commit

Permalink
Merge pull request julycoding#390 from muzichao/patch-1
Browse files Browse the repository at this point in the history
Update 03.01.md
  • Loading branch information
julycoding committed Oct 29, 2014
2 parents c6a084b + a6accf9 commit ab6ed51
Showing 1 changed file with 35 additions and 35 deletions.
70 changes: 35 additions & 35 deletions ebook/zh/03.01.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

二叉查找树(Binary Search Tree),也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或者具有下列性质的二叉树:

- 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 任意节点的左、右子树也分别为二叉查找树。
- 没有键值相等的节点(no duplicate nodes)。
- 若任意结点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若任意结点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 任意结点的左、右子树也分别为二叉查找树。
- 没有键值相等的结点(no duplicate nodes)。

因为,一棵由n个结点,随机构造的二叉查找树的高度为lgn,所以顺理成章,一般操作的执行时间为O(lgn).(至于n个结点的二叉树高度为lgn的证明,可参考算法导论 第12章 二叉查找树 第12.4节)。

Expand All @@ -34,7 +34,7 @@

![](../images/rbtree/1.png)

上文中我们所说的 "叶结点" 或"NULL结点",它不包含数据而只充当树在此结束的指示,这些节点以及它们的父结点,在绘图中都会经常被省略。
上文中我们所说的 "叶结点" 或"NULL结点",它不包含数据而只充当树在此结束的指示,这些结点以及它们的父结点,在绘图中都会经常被省略。

### 树的旋转知识

Expand Down Expand Up @@ -141,8 +141,8 @@ RB-INSERT(T, z)

但当遇到下述3种情况时:
- 插入修复情况1:如果当前结点的父结点是红色且祖父结点的另一个子结点(叔叔结点)是红色
- 插入修复情况2:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子
- 插入修复情况3:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子
- 插入修复情况2:当前结点的父结点是红色,叔叔结点是黑色,当前结点是其父结点的右子
- 插入修复情况3:当前结点的父结点是红色,叔叔结点是黑色,当前结点是其父结点的左子

又该如何调整呢?答案就是根据红黑树插入代码RB-INSERT(T, z)最后一行调用的RB-INSERT-FIXUP(T,z)所示操作进行,具体如下所示:
```
Expand Down Expand Up @@ -184,7 +184,7 @@ RB-INSERT-FIXUP(T,z)
在此,我们只考虑父结点为祖父左子的情况。
同时,还可以分为当前结点是其父结点的左子还是右子,但是处理方式是一样的。我们将此归为同一类。

对策:将当前节点的父节点和叔叔节点涂黑,祖父结点涂红,把当前结点指向祖父节点,从新的当前节点重新开始算法。即如下代码所示:
对策:将当前结点的父结点和叔叔结点涂黑,祖父结点涂红,把当前结点指向祖父结点,从新的当前结点重新开始算法。即如下代码所示:

```
5 then color[p[z]] ← BLACK ▹ Case 1
Expand All @@ -193,31 +193,31 @@ RB-INSERT-FIXUP(T,z)
8 z ← p[p[z]] ▹ Case 1
```

针对情况1,变化前(图片来源:saturnman)[当前节点为4节点]
针对情况1,变化前(图片来源:saturnman)[当前结点为4结点]

![](../images/rbtree/4.png)

变化后:

![](../images/rbtree/5.png)

**插入修复情况2:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子**
对策:当前节点的父节点做为新的当前节点,以新当前节点为支点左旋。即如下代码所示:
**插入修复情况2:当前结点的父结点是红色,叔叔结点是黑色,当前结点是其父结点的右子**
对策:当前结点的父结点做为新的当前结点,以新当前结点为支点左旋。即如下代码所示:
```
9 else if z = right[p[z]]
10 then z ← p[z] ▹ Case 2
11 LEFT-ROTATE(T, z) ▹ Case 2
```
如下图所示,变化前[当前节点为7节点]
如下图所示,变化前[当前结点为7结点]

![](../images/rbtree/6.png)

变化后:

![](../images/rbtree/7.png)

**插入修复情况3:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子**
解法:父节点变为黑色,祖父节点变为红色,在祖父节点为支点右旋,操作代码为:
**插入修复情况3:当前结点的父结点是红色,叔叔结点是黑色,当前结点是其父结点的左子**
解法:父结点变为黑色,祖父结点变为红色,在祖父结点为支点右旋,操作代码为:
```
12 color[p[z]] ← BLACK ▹ Case 3
13 color[p[p[z]]] ← RED ▹ Case 3
Expand All @@ -226,7 +226,7 @@ RB-INSERT-FIXUP(T,z)

最后,把根结点涂为黑色,整棵红黑树便重新恢复了平衡。

如下图所示[当前节点为2节点]
如下图所示[当前结点为2结点]

![](../images/rbtree/8.png)

Expand All @@ -240,15 +240,15 @@ RB-INSERT-FIXUP(T,z)

ok,接下来,咱们最后来了解,红黑树的删除操作。

"我们删除的节点的方法与常规二叉搜索树中删除节点的方法是一样的,如果被删除的节点不是有双非空子女,则直接删除这个节点,用它的唯一子节点顶替它的位置,如果它的子节点分是空节点,那就用空节点顶替它的位置,如果它的双子全为非空,我们就把它的直接后继节点内容复制到它的位置,之后以同样的方式删除它的后继节点,它的后继节点不可能是双子非空,因此此传递过程最多只进行一次。”
"我们删除的结点的方法与常规二叉搜索树中删除结点的方法是一样的,如果被删除的结点不是有双非空子女,则直接删除这个结点,用它的唯一子结点顶替它的位置,如果它的子结点分是空结点,那就用空结点顶替它的位置,如果它的双子全为非空,我们就把它的直接后继结点内容复制到它的位置,之后以同样的方式删除它的后继结点,它的后继结点不可能是双子非空,因此此传递过程最多只进行一次。”

#### 二叉查找树的删除

继续讲解之前,补充说明下二叉树结点删除的几种情况,待删除的节点按照儿子的个数可以分为三种
继续讲解之前,补充说明下二叉树结点删除的几种情况,待删除的结点按照儿子的个数可以分为三种

1. 没有儿子,即为叶结点。直接把父结点的对应儿子指针设为NULL,删除儿子结点就OK了。
2. 只有一个儿子。那么把父结点的相应儿子指针指向儿子的独生子,删除儿子结点也OK了。
3. 有两个儿子。这是最麻烦的情况,因为你删除节点之后,还要保证满足搜索二叉树的结构。其实也比较容易,我们可以选择左儿子中的最大元素或者右儿子中的最小元素放到待删除节点的位置,就可以保证结构的不变。当然,你要记得调整子树,毕竟又出现了节点删除。习惯上大家选择左儿子中的最大元素,其实选择右儿子的最小元素也一样,没有任何差别,只是人们习惯从左向右。这里咱们也选择左儿子的最大元素,将它放到待删结点的位置。左儿子的最大元素其实很好找,只要顺着左儿子不断的去搜索右子树就可以了,直到找到一个没有右子树的结点。那就是最大的了。
3. 有两个儿子。这是最麻烦的情况,因为你删除结点之后,还要保证满足搜索二叉树的结构。其实也比较容易,我们可以选择左儿子中的最大元素或者右儿子中的最小元素放到待删除结点的位置,就可以保证结构的不变。当然,你要记得调整子树,毕竟又出现了结点删除。习惯上大家选择左儿子中的最大元素,其实选择右儿子的最小元素也一样,没有任何差别,只是人们习惯从左向右。这里咱们也选择左儿子的最大元素,将它放到待删结点的位置。左儿子的最大元素其实很好找,只要顺着左儿子不断的去搜索右子树就可以了,直到找到一个没有右子树的结点。那就是最大的了。

二叉查找树的删除代码如下所示:
```
Expand Down Expand Up @@ -298,7 +298,7 @@ RB-DELETE(T, z) 单纯删除结点的总操作
18 return y
```

在删除节点后,原红黑树的性质可能被改变,如果删除的是红色节点,那么原红黑树的性质依旧保持,此时不用做修正操作,如果删除的节点是黑色节点,原红黑树的性质可能会被改变,我们要对其做修正操作。那么哪些树的性质会发生变化呢,如果删除节点不是树唯一节点,那么删除节点的那一个支的到各叶节点的黑色节点数会发生变化,此时性质5被破坏。如果被删节点的唯一非空子节点是红色,而被删节点的父节点也是红色,那么性质4被破坏。如果被删节点是根节点,而它的唯一非空子节点是红色,则删除后新根节点将变成红色,违背性质2。”
在删除结点后,原红黑树的性质可能被改变,如果删除的是红色结点,那么原红黑树的性质依旧保持,此时不用做修正操作,如果删除的结点是黑色结点,原红黑树的性质可能会被改变,我们要对其做修正操作。那么哪些树的性质会发生变化呢,如果删除结点不是树唯一结点,那么删除结点的那一个支的到各叶结点的黑色结点数会发生变化,此时性质5被破坏。如果被删结点的唯一非空子结点是红色,而被删结点的父结点也是红色,那么性质4被破坏。如果被删结点是根结点,而它的唯一非空子结点是红色,则删除后新根结点将变成红色,违背性质2。”

RB-DELETE-FIXUP(T, x) 恢复与保持红黑性质的工作

Expand Down Expand Up @@ -328,28 +328,28 @@ RB-DELETE-FIXUP(T, x) 恢复与保持红黑性质的工作
23 color[x] ← BLACK
```

“上面的修复情况看起来有些复杂,下面我们用一个分析技巧:我们从被删节点后来顶替它的那个节点开始调整,并认为它有额外的一重黑色。这里额外一重黑色是什么意思呢,我们不是把红黑树的节点加上除红与黑的另一种颜色,这里只是一种假设,我们认为我们当前指向它,因此空有额外一种黑色,可以认为它的黑色是从它的父节点被删除后继承给它的,它现在可以容纳两种颜色,如果它原来是红色,那么现在是红+黑,如果原来是黑色,那么它现在的颜色是黑+黑。有了这重额外的黑色,原红黑树性质5就能保持不变。现在只要恢复其它性质就可以了,做法还是尽量向根移动和穷举所有可能性。"--saturnman。
“上面的修复情况看起来有些复杂,下面我们用一个分析技巧:我们从被删结点后来顶替它的那个结点开始调整,并认为它有额外的一重黑色。这里额外一重黑色是什么意思呢,我们不是把红黑树的结点加上除红与黑的另一种颜色,这里只是一种假设,我们认为我们当前指向它,因此空有额外一种黑色,可以认为它的黑色是从它的父结点被删除后继承给它的,它现在可以容纳两种颜色,如果它原来是红色,那么现在是红+黑,如果原来是黑色,那么它现在的颜色是黑+黑。有了这重额外的黑色,原红黑树性质5就能保持不变。现在只要恢复其它性质就可以了,做法还是尽量向根移动和穷举所有可能性。"--saturnman。

如果是以下情况,恢复比较简单:
- a)当前节点是红+黑色
解法,直接把当前节点染成黑色,结束此时红黑树性质全部恢复。
- b)当前节点是黑+黑且是根节点
- a)当前结点是红+黑色
解法,直接把当前结点染成黑色,结束此时红黑树性质全部恢复。
- b)当前结点是黑+黑且是根结点
解法:什么都不做,结束。

但如果是以下情况呢?:
- 删除修复情况1:当前节点是黑+黑且兄弟节点为红色(此时父节点和兄弟节点的子节点分为黑)
- 删除修复情况2:当前节点是黑加黑且兄弟是黑色且兄弟节点的两个子节点全为黑色
- 删除修复情况3:当前节点颜色是黑+黑,兄弟节点是黑色,兄弟的左子是红色,右子是黑色
- 删除修复情况4:当前节点颜色是黑-黑色,它的兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意
- 删除修复情况1:当前结点是黑+黑且兄弟结点为红色(此时父结点和兄弟结点的子结点分为黑)
- 删除修复情况2:当前结点是黑加黑且兄弟是黑色且兄弟结点的两个子结点全为黑色
- 删除修复情况3:当前结点颜色是黑+黑,兄弟结点是黑色,兄弟的左子是红色,右子是黑色
- 删除修复情况4:当前结点颜色是黑-黑色,它的兄弟结点是黑色,但是兄弟结点的右子是红色,兄弟结点左子的颜色任意


此时,我们需要调用RB-DELETE-FIXUP(T, x),来恢复与保持红黑性质的工作。

下面,咱们便来分别处理这4种删除修复情况。

**删除修复情况1:当前节点是黑+黑且兄弟节点为红色(此时父节点和兄弟节点的子节点分为黑)。**
**删除修复情况1:当前结点是黑+黑且兄弟结点为红色(此时父结点和兄弟结点的子结点分为黑)。**

解法:把父节点染成红色,把兄弟结点染成黑色,之后重新进入算法(我们只讨论当前节点是其父节点左孩子时的情况)。此变换后原红黑树性质5不变,而把问题转化为兄弟节点为黑色的情况(注:变化前,原本就未违反性质5,只是为了**把问题转化为兄弟节点为黑色的情况**)。 即如下代码操作:
解法:把父结点染成红色,把兄弟结点染成黑色,之后重新进入算法(我们只讨论当前结点是其父结点左孩子时的情况)。此变换后原红黑树性质5不变,而把问题转化为兄弟结点为黑色的情况(注:变化前,原本就未违反性质5,只是为了**把问题转化为兄弟结点为黑色的情况**)。 即如下代码操作:
```
//调用RB-DELETE-FIXUP(T, x) 的1-8行代码
1 while x ≠ root[T] and color[x] = BLACK
Expand All @@ -369,9 +369,9 @@ RB-DELETE-FIXUP(T, x) 恢复与保持红黑性质的工作

![](../images/rbtree/11.jpg)

**删除修复情况2:当前节点是黑加黑且兄弟是黑色且兄弟节点的两个子节点全为黑色**
**删除修复情况2:当前结点是黑加黑且兄弟是黑色且兄弟结点的两个子结点全为黑色**

解法:把当前节点和兄弟节点中抽取一重黑色追加到父节点上,把父节点当成新的当前节点,重新进入算法。(此变换后性质5不变),即调用RB-INSERT-FIXUP(T, z) 的第9-10行代码操作,如下:
解法:把当前结点和兄弟结点中抽取一重黑色追加到父结点上,把父结点当成新的当前结点,重新进入算法。(此变换后性质5不变),即调用RB-INSERT-FIXUP(T, z) 的第9-10行代码操作,如下:
```
//调用RB-DELETE-FIXUP(T, x) 的9-11行代码
9 if color[left[w]] = BLACK and color[right[w]] = BLACK
Expand All @@ -386,9 +386,9 @@ RB-DELETE-FIXUP(T, x) 恢复与保持红黑性质的工作

![](../images/rbtree/13.jpg)

**删除修复情况3:当前节点颜色是黑+黑,兄弟节点是黑色,兄弟的左子是红色,右子是黑色。**
**删除修复情况3:当前结点颜色是黑+黑,兄弟结点是黑色,兄弟的左子是红色,右子是黑色。**

解法:把兄弟结点染红,兄弟左子节点染黑,之后再在兄弟节点为支点解右旋,之后重新进入算法。此是把当前的情况转化为情况4,而性质5得以保持,即调用RB-INSERT-FIXUP(T, z) 的第12-16行代码,如下所示:
解法:把兄弟结点染红,兄弟左子结点染黑,之后再在兄弟结点为支点解右旋,之后重新进入算法。此是把当前的情况转化为情况4,而性质5得以保持,即调用RB-INSERT-FIXUP(T, z) 的第12-16行代码,如下所示:
```
//调用RB-DELETE-FIXUP(T, x) 的第12-16行代码
12 else if color[right[w]] = BLACK
Expand All @@ -405,9 +405,9 @@ RB-DELETE-FIXUP(T, x) 恢复与保持红黑性质的工作

![](../images/rbtree/15.jpg)

**删除修复情况4:当前节点颜色是黑-黑色,它的兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意**
**删除修复情况4:当前结点颜色是黑-黑色,它的兄弟结点是黑色,但是兄弟结点的右子是红色,兄弟结点左子的颜色任意**

解法:把兄弟节点染成当前节点父节点的颜色,把当前节点父节点染成黑色,兄弟节点右子染成黑色,之后以当前节点的父节点为支点进行左旋,此时算法结束,红黑树所有性质调整正确,即调用RB-INSERT-FIXUP(T, z)的第17-21行代码,如下所示:
解法:把兄弟结点染成当前结点父结点的颜色,把当前结点父结点染成黑色,兄弟结点右子染成黑色,之后以当前结点的父结点为支点进行左旋,此时算法结束,红黑树所有性质调整正确,即调用RB-INSERT-FIXUP(T, z)的第17-21行代码,如下所示:
```
//调用RB-DELETE-FIXUP(T, x) 的第17-21行代码
17 color[w] ← color[p[x]] ▹ Case 4
Expand Down

0 comments on commit ab6ed51

Please sign in to comment.