二叉搜索树
有如下性质:
1. 对任意某一结点,其左子树(若存在)的任一结点比该结点小,其右子树(若存在)的任一结点比该节点大。
2. 每个结点都有一个作为搜索依据的关键码(key),所有结点的关键码互不相同。
其它一些重要结论:
如果二叉查找树中的某个结点有两个子女,则其后继没有左孩子,其前趋没有右孩子。
后继定义:对结点x而言,存在一个结点集合,集合内任一结点的key值都比x的key值大。而结点x的后继,既是该集合内的最小key值的结点。
寻找后继 伪代码:
TREE_SUCCESSOR(x)
if right(x) != NIL
//若目标结点x的右子树非空,则对右子树调用TREE_MINIMUM()得出后继
then return TREE_MINIMUM(right[x])
y <- p[x] //令y做结点x的父结点
//若y结点非空,则当发现结点x是父结点y的左孩子时,y即为结点x后继
while y != NIL and x == right[y]
do x <- y
y <- p[y]
return y
寻找前趋 伪代码
TREE_PREDECESSOR(x)
//寻找前趋的过程与寻找后继过程对称的
if left(x) != NIL
then return TREE_MAXIMUM(left[x])
y <- p[x]
while y != NIL x == left[x]
do x <- y
y <- p[y]
return y
插入元素
伪代码:
//T参数指该树,z结点是将要插入的结点
TREE_INSERT(T, z)
//x结点是当前结点,y结点是x结点的父结点
y<-NIL;
x<-y;
//如果x为空,说明了当前结点已经到达最底部
//并且 这个NIL所占的位置,既是z结点要插入的位置
while x != NIL
do y <- x //将父结点指向当前结点
if key[z] < key[x]
//如果目标结点z小于当前结点x
//则令当前结点x向左结点走
then x <- left[x]
//否则,当前结点x向右结点走
else x <- right[x]
//使得z结点parent指针指向y结点
p[z] <- y
if y=NIL
//若树T是空的,则z结点直接插入到树的根结点
then root[T] <- z
//否则,根据结点z与y的key值大小比较,向左或者向右插入z结点
else if key[z] < key[y]
then left[y] <- z
else right[y] <- z
删除结点
仔细想想,目标结点有三种情况
1. 目标结点为叶子结点,则可以直接删除
2. 目标结点只有一个孩子,则可以使目标结点的孩子的parent指针指向目标结点的父结点,使目标结点的父结点指向目标结点的孩子结点,然后删除目标结点
3. 目标结点有两个孩子结点,则使目标结点的后继替换目标结点的位置,而由于上面提到过的结论(如果二叉查找树中的某个结点有两个子女,则其后继没有做子女,其前趋没有右子女。)可知,其后继没有左孩子,则使其后继的右孩子与后继的父结点关联起来即可。然后用后继替代目标结点。对于第3种情况,有一个比较特殊的情况是,目标结点的右孩子是其后继。
算法导论第三版的删除节点的思路:
- 目标节点的左孩子为NIL,则直接由其右孩子代替其本身,无论右孩子是否为NIL,如图a
- 目标节点的左孩子非NIL,且右孩子为NIL,则由左孩子代替其本身,如图b
- 目标节点的两个孩子非NIL,且后继是其右孩子,则直接有其右孩子代替其本身,如图c
- 目标节点的两个孩子非NIL,且后继不是右孩子,则先将后继的右孩子(无论是否NIL)代替后继本身(该后继必定无左孩子),然后用后继替换目标节点,如图d
删除结点 伪代码:
TREE_DELETE(T, z)
if z.left == NIL
TRANSPLANT(T, z, z.right)
elseif z.right == NIL
TRANSPLANT(T, z, z.left)
else y = TREE_MININUM(z.right)
if y.p != z
TRANSPLANT(T, y, y.right)
y.right = z.right
y.right.p = y
TRANSPLANT(T, z, y)
y.left = z.left
y.left.p = y
而TRANSPLANT函数用于子树的替换
TRANSPLANT(T, u, e)
if u.p == NIL
T.root = v
elseif u.p.left == u
u.p.left = v
else
u.p.right = v
if v != NIL
v.p = u.p