学习笔记_树
哨兵 NIL
哨兵sentinel是一个哑dummy对象, 可以用来简化边界条件;
可以用在环形双向链表中, nil介于头和尾之间
二叉查找树
注意点
- 根节点是树中唯一父节点为nil的节点
特性
- 如果一个节点y是节点x的左子树,则key[y] < key[x]
- 如果一个节点y是节点x的右子树,则key[y] > key[x]
遍历-中序遍历
获得的查询结果是递增的
INORDER-TREE-WALK(x)
if x != nil
then INORDER-TREE-WALK(left[x])
printf(x)
then INORDER-TREE-WALK(left[x])
查找
ITERATIVE-TREE-SEARCH(x, k)
while x!=nil or k!=key[x]
do if k < key[x]
then x = left[x]
else x = right[x]
return x
最小值
TREE-MINIIMUM(x)
while left[x] != nil
else x = left[x]
return x
最大值
TREE-MAXIMUM(x)
while right[x] != nil
else x = right[x]
return x
后继
给定一个节点,找出中序遍历下它的后继
TREE-subccessor
if right[x] != nil //如果x有右子树,则查找右子树的最小值
then TREE-MINIMUN(right[x])
y = p[x] //如果x是左子树,则返回p
while y != nil and x == right[y] //否则一直会找到根节点
do x = y
y = p[y]
return y
前趋
给定一个节点,找出中序遍历下它的前趋
TREE-predecessor
y = p[x] //如果x是左子树,则返回p
while y != nil and x == right[y] //否则一直会找到根节点
do x = y
y = p[y]
return y
插入
TREE-INSERT(T,z)
y = nil
x = root[T]
while x != nil
do y = x
if key[z] < key[x]
then x = left[x]
else x = right[x]
p[z] = y // 查找下来x=nil的位置即为要插入的位置
if y == nil
then root[T] = z //说明是一个空树
else if key[z] < key[y]
then left[y] = z
else right[y] = z
删除
- Z有0个子女, 则p[z] = nil
- z有1个子女, 则将z的父和子相连来将z删除
- z有2个子女, 先删除z的后继y, 再用y的内容来替代z的内容
TREE-DELETE(T,z)
if left[z] == nil or right[z] == nil
then y = z
else y = TREE-SUCCESSOR(z) // 1-3行确定要删除的节点y,是z还是z后继
if left[y] != nil
then x = left[y]
else x = right[y] // 4-6行x被置为y的非nil子女(都nil则nil)
if x == nil
then p[x] = p[y]
if p[y] == nil
then root[T] == x
else if y == left[p[y]]
then left[p[y]] = x
else right[p[y]] = x // 7-13行,通过修改p[y]和x的指针将y删除
if y != z
then key[z] = keyp[y]
"copy z's satellite data into z"//如果要删除的是z的后继,则将后继数据复制到z中
return y; // 返回要删除的节点y
红黑树
目的
确保没有一条路径会比其他路径长出两倍, 因而接近平衡
为了平衡黑色的高度, 以减少检索的深度, 左右相差不超过2倍
对于红黑树的恢复, 是将红色节点不断向根节点移动, 然后将根节点变为黑色
特性
- 每个节点是红的或者黑的
- 根节点是黑色的
- 每个叶节点(nil)是黑色的
- 如果一个节点是红色的,则它的两个儿子都是黑色的
- 对每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点( 黑高 一样_)
哨兵的使用
采用一个哨兵nil[T]来代表所有的NIL, 以节省空间和简化逻辑
左旋
指针结构的修改是用过旋转来完成的, 这是一种能保持二叉查找树性质的查找树局部操作.
假设x可以是树内任意右孩子不是nil[T]的节点,
在节点x上左旋, 让y为x的右孩子
左旋以x到y之间的链为"支轴"进行
使y称为该子树新的根, x称为y的左孩子, y的左孩子称为x的右孩子
- y = x->right
- x.right -> y.left
- y.parent -> x.parent
- x.parent -> y.parent
LEFT-ROTATE(T,x)
y = right[x]
right[x] = right[y]
p[left[y]] = x //将x的右子树指向y的左子树
p[y] = p[x]
if p[x] == nil[T]
then root[T] = y
else if x == left[p[x]]
then left[p[x]] = y
else right[p[x]] = y // 将x的父指针指向y
left[y] = x
p[x] = y // 将y的左子树指向x
右旋, 节点的左边向右旋转
将左旋的x改成y, left改成right, right改成left
插入
RBTREE-INSERT(T,z)
y = nil[T]
x = root[T]
while x != nil[T]
do y = x
if key[z] < key[x]
then x = left[x]
else x = right[x]
p[z] = y // 查找下来x=nil的位置即为要插入的位置
if y == nil[T]
then root[T] = z //说明是一个空树
else if key[z] < key[y]
then left[y] = z
else right[y] = z
left[z] = nil[T]
right[z] = nil[T]
color[z] = RED
RB-INSERT-FIXUP(T, z)
破坏了特性2(根节点必须为黑色)和性质4(红节点不能右红子女)
// 父节点是红色, 则祖父节点是黑色的,叔父节点是红黑之一
// 1.叔父节点是红色的
// 2.叔父节点是黑色的,当前节点是父亲节点的左节点
// 3.叔父节点是黑色的,当前节点是父亲节点的右节点
RB-INSERT-FIXUP(T, z)
while color[p[z]] == RED // 父节点是红色, 则祖父节点是黑色的,叔父节点是红黑之一
do
if p[z] == left[p[p[z]]]
then y = right[p[p[z]]] // 获取叔父节点,先判断父节点是左的情况
if color[y] == RED // 情况1,如果叔父节点是红色的
then color[p[z]] = BLCAK // 情况1
color[y] = BLACK // 情况1
color[p[p[z]]] = RED // 情况1
z = p[p[z]] // 情况1
else
if z == right[p[z]]
then z = p[z] // 情况2,叔父节点黑色,当前节点左
LEFT-ROTATE(T,z) // 情况2
color[p[z]] = BLACK // 情况3,叔父节点黑色,当前节点右
color[p[p[z]]] = RED // 情况3
RIGHT-ROTATE(T, p[p[z]]) // 情况3
else (same as clause with "right" and "left" exchanged)
color[root[T]] = BLACK
删除
- Z有0个子女, 则p[z] = nil
- z有1个子女, 则将z的父和子相连来将z删除
- z有2个子女, 先删除z的后继y, 再用y的内容来替代z的内容
RBTREE-DELETE(T,z)
if left[z] == nil[T] or right[z] == nil[T]
then y = z
else y = TREE-SUCCESSOR(z) // 1-3行确定要删除的节点y,是z还是z后继
if left[y] != nil[T]
then x = left[y]
else x = right[y] // 4-6行x被置为y的非nil子女(都nil则nil)
p[x] = p[y]
if p[y] == nil[T]
then root[T] == x
else if y == left[p[y]]
then left[p[y]] = x
else right[p[y]] = x // 7-13行,通过修改p[y]和x的指针将y删除
if y != z
then key[z] = keyp[y]
"copy z's satellite data into z"//如果要删除的是z的后继,则将后继数据复制到z中
if(color[y]=BLACK)
then RB-DELETE-FIXUP(T, x)
return y // 返回要删除的节点y
B树
对于3阶, 则每个节点最多存放2个数据, 最多具有3个子树
根据 Knuth 的定义,一个 m 阶的B树是一个有以下属性的树:
- 每一个节点最多有 m 个子节点
- 每一个非叶子节点(除根节点)最少有 ⌈m/2⌉ 个子节点
- 如果根节点不是叶子节点,那么它至少有两个子节点
- 有 k 个子节点的非叶子节点拥有 k − 1 个键
- 所有的叶子节点都在同一层
如果一个内部节点有3个子节点(子树),那么它就必须有两个键: a1 和 a2 。左边子树的所有值都必须小于 a1 ,中间子树的所有值都必须在 a1 和a2 之间,右边子树的所有值都必须大于 a2 。
2-3-4树(4阶B树)
B+树(中间节点只保存索引,所有数据保存在叶子节点)
一个m阶的B+树具有如下几个特征:
- 有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点。
- 所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
- 所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
学习链接
<<算法导论>>