Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
OpenDocCN
cs61b-textbook-zh
提交
0444b218
C
cs61b-textbook-zh
项目概览
OpenDocCN
/
cs61b-textbook-zh
通知
3
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
C
cs61b-textbook-zh
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
0444b218
编写于
5月 21, 2019
作者:
A
Abel-Huang
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
add 9.1.0
上级
12243b1d
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
183 addition
and
0 deletion
+183
-0
zh/9.md
zh/9.md
+183
-0
zh/img/figure_9_1.png
zh/img/figure_9_1.png
+0
-0
zh/img/figure_9_2.jpg
zh/img/figure_9_2.jpg
+0
-0
未找到文件。
zh/9.md
0 → 100644
浏览文件 @
0444b218
# 第九章 平衡搜索
> 译者:[Abel-Huang](https://github.com/Abel-Huang)
我们之前已经看到二叉搜索树有一个弱点:左右子树趋向于不平衡,因此无法将它们所表示的数据集划分成两个同样大小的部分。我们考虑一下我们可以对此做些什么改进。
当然,我们总是可以通过简单地将所有节点按顺序进行排序,然后重新插入它们以生成一个新的平衡二叉树。然而这种操作需要花费树节点数线性规模的复杂度,并且可以看出在 插入N个元素很难避免退化为$
\T
heta(N^2)$的复杂度。相比之下,如果数据碰巧以保持树遍历的顺序,则进行N次插入仅需要$
\T
heta(N
\l
gN)$的时间。因此,让我们首先看看重新平衡树(或保持平衡)的操作,而不将其拆开并重新构建。
## 9.1 平衡构造:B-树
保持搜索树平衡的另一种方法是始终小心"在一个正确的位置插入新的树节点",以便树通过构造器保持平衡。数据库社区长期以来一直使用完全符合这个要求的数据结构:
*B-tree*
。我们将在这里抽象地描述这种数据结构和相关操作,而不是直接给出代码,因为这种数据结构用于实践中大量用于提高速度的存储设备。
*m阶B-树*
是具有以下属性的多叉树:
1.
每个节点孩子数最多为m个。
2.
除根之外的所有节点至少有m/2个子节点(我们也可以说除根节点外的每个节点至少包含⌈m/2⌉个子节点)。
3.
一个节点的孩子$C_0$, $C_1$, ..., $C_(n-1)$用键$K_1$, ..., $K_(n-1)$标记(认为$K_i$在$C_(i-1)$和$C_i$之间),其中$k_1$<$k_2$<...<$k_(n-1)$;
4.
B-树是一种搜索树:对于任何一个节点,以$C_i$为根的子树中所有键都严格地小于$K_(i+1)$,并且(对于i > 0)严格地大于$K_i$。
5.
所有空孩子都出现在树的同一层。
图9.1包含一个4阶排序树的示例。在实际实现中,B-树一般用在在二级存储(如磁盘等)上,根据实际需要读入它们的节点。我们选择m阶树(多叉数)的目的是尽可能快地从二级存储器传输数据。对于磁盘操作而言更关注的是单次磁盘访问所需要时间越小越好,
因此m的值范围越大,从不同节点中读取的时间差异越小。在这种情况下,
**m**
取的值过小很明显不是一个很好的主意。
我们将用一个被称为
**BTreeNode**
的结构来表示B-树的节点,并且有如下的定义:
**B.child(i)**
:B-树节点
**B**
的第
*i*
个孩子,
**0 <= i < m**
。
**B.key(i)**
:B-树节点
**B**
的第
*i*
个关键字Key,
**1 <= i < m**
。
**B.parent()**
:B-树节点
**B**
的父节点。
**B.index()**
:B-树节点
**B**
的索引
*i*
,
**B == B.parent().child(i)**
。
**B.arity()**
:B-树节点
**B**
的孩子数。
然后,整个B-树由指向根的指针组成,可能还包含一些额外的有用信息,例如B-树当前的大小。
基于属性2和属性5,一个拥有N个关键字的B-树一定有$
\T
heta(
\l
og_(m/2)N)$层节点。基于属性1,搜索单个节点的关键字需要O(1)时间(保证m为固定值)。因此通过下面的递归算法搜索B-树是一个$
\T
heta(
\l
gN)$级别的操作。
```
java
boolean
search
(
BTreeNode
B
,
Key
X
)
{
if
(
B
is
the
empty
tree
)
return
false
;
else
{
Find
largest
c
such
that
B
.
key
(
i
)
≤
X
,
for
all
1
≤
i
≤
c
.
if
(
c
>
0
&&
X
.
equals
(
B
.
key
(
c
)))
return
true
;
else
return
search
(
B
.
child
(
c
),
K
);
}
```
![
figure_9_1
](
https://github.com/Abel-Huang/cs61b-textbook-zh/blob/master/zh/img/figure_9_1.png
)
图9.1: 键为整型的4阶B-树示例。圆圈代表空节点,它们出现在树的同一层级。每个节点拥有2到4个子节点,每个节点用于1到3个关键字。每个键都大于其左侧子节点中的所有键,并且小于右侧子节点中的所有键
### 9.1.1 插入
### 9.1.2 删除
![
figure_9_2
](
https://github.com/Abel-Huang/cs61b-textbook-zh/blob/master/zh/img/figure_9_2.jpg
)
### 9.1.3 红黑树
## 9.2 字典树
### 9.2.1 基本属性和算法
```
java
public
abstract
class
Trie
{
/** The empty Trie. */
public
static
final
Trie
EMPTY
=
new
EmptyTrie
();
/** The label at this node. Defined only on leaves. */
abstract
public
String
label
();
/** True if X is in this Trie. */
public
boolean
isIn
(
String
x
)
...
/** The result of inserting X into this Trie, if it is not
* already there, and returning this. This trie is
* unchanged if X is in it already. */
public
Trie
insert
(
String
x
)
...
/** The result of removing X from this Trie, if it is present.
* The trie is unchanged if X is not present. */
public
Trie
remove
(
String
x
)
...
/** True if this Trie is a leaf (containing a single String). */
abstract
public
boolean
isLeaf
();
/** True if this Trie is empty */
abstract
public
boolean
isEmpty
();
/** The child numbered with character K. Requires that this node
* not be empty. Child 0 corresponds to ✷. */
abstract
public
Trie
child
(
int
k
);
/** Set the child numbered with character K to C. Requires that
* this node not be empty. (Intended only for internal use. */
abstract
protected
void
setChild
(
int
k
,
Trie
C
);
}
```
```
java
/** True if X is in this Trie. */
public
boolean
isIn
(
String
x
)
{
Trie
P
=
longestPrefix
(
x
,
0
);
return
P
.
isLeaf
()
&&
x
.
equals
(
P
.
label
());
}
/** The node representing the longest prefix of X.substring(K) that
* matches a String in this trie. */
private
Trie
longestPrefix
(
String
x
,
int
k
)
{
if
(
isEmpty
()
||
isLeaf
())
return
this
;
int
c
=
nth
(
x
,
k
);
if
(
child
(
c
).
isEmpty
())
return
this
;
else
return
child
(
c
).
longestPrefix
(
x
,
k
+
1
);
}
/** Character K of X, or ✷ if K is off the end of X. */
static
char
nth
(
String
x
,
int
k
)
{
if
(
k
>=
x
.
length
())
return
(
char
)
0
;
else
return
x
.
charAt
(
k
);
}
```
### 9.2.2 表示
```
java
class
EmptyTrie
extends
Trie
{
public
boolean
isEmpty
()
{
return
true
;
}
public
boolean
isLeaf
()
{
return
false
;
}
public
String
label
()
{
throw
new
Error
(...);
}
public
Trie
child
(
int
c
)
{
throw
new
Error
(...);
}
protected
void
child
(
int
c
,
Trie
T
)
{
throw
new
Error
(...);
}
}
class
LeafTrie
extends
Trie
{
private
String
L
;
/** A Trie containing just the string S. */
LeafTrie
(
String
s
)
{
L
=
s
;
}
public
boolean
isEmpty
()
{
return
false
;
}
public
boolean
isLeaf
()
{
return
true
;
}
public
String
label
()
{
return
L
;
}
public
Trie
child
(
int
c
)
{
return
EMPTY
;
}
protected
void
child
(
int
c
,
Trie
T
)
{
throw
new
Error
(...);
}
}
class
InnerTrie
extends
Trie
{
// ALPHABETSIZE has to be defined somewhere */
private
Trie
[]
kids
=
new
kids
[
ALPHABETSIZE
];
/** A Trie with child(K) == T and all other children empty. */
InnerTrie
(
int
k
,
Trie
T
)
{
for
(
int
i
=
0
;
i
<
kids
.
length
;
i
+=
1
)
kids
[
i
]
=
EMPTY
;
child
(
k
,
T
);
}
public
boolean
isEmpty
()
{
return
false
;
}
public
boolean
isLeaf
()
{
return
false
;
}
public
String
label
()
{
throw
new
Error
(...);
}
public
Trie
child
(
int
c
)
{
return
kids
[
c
];
}
protected
void
child
(
int
c
,
Trie
T
)
{
kids
[
c
]
=
T
;
}
}
```
### 9.2.3 表压缩
```
java
class
InnerTrie
extends
Trie
{
private
static
char
[]
charMap
=
new
char
[
’
9
’
+
1
];
static
{
charMap
[
0
]
=
0
;
charMap
[
’
0
’
]
=
1
;
charMap
[
’
1
’
]
=
1
;
...
}
public
Trie
child
(
int
c
)
{
return
kids
[
charMap
[
c
]];
}
protected
void
child
(
int
c
,
Trie
T
)
{
kids
[
charMap
[
c
]]
=
T
;
}
}
```
## 9.3 旋转自平衡树
### 9.3.1 AVL树
## 9.4 伸展树
### 9.4.1 分析
## 9.5 跳表
\ No newline at end of file
zh/img/figure_9_1.png
0 → 100644
浏览文件 @
0444b218
43.5 KB
zh/img/figure_9_2.jpg
0 → 100644
浏览文件 @
0444b218
90.7 KB
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录