算法导论读书笔记(12)
二叉查找树
以下图所示,一棵二叉查找树是按二叉树结构来组织的。这样的树能够用链表结构来表示,其中每个结点都是一个对象。结点中除了 key 域和卫星数据外,还包含域 left , right 和 p ,它们分别指向结点的左儿子,右儿子和父结点。若是某个儿子结点或父结点不存在,则相应域中的值为 NIL
。根结点是树中惟一的父结点为 NIL
的结点。算法
在二叉查找树(binary search tree)上执行的基本操做的时间与树的高度成正比。对于一棵含 n 个结点的彻底二叉树,这些操做的最坏状况运行时间为 Θ ( lg n )。可是,若是树是含 n 个结点的线性链,则这些操做的最坏状况运行时间为 Θ ( n )。数据结构
二叉查找树中关键字的存储方式老是知足如下的 二叉查找树 性质:指针
设 x 为二叉查找树中的一个结点。若是 y 是 x 的左子树中的一个结点,则 y.key <= x.key 。若是 y 是 x 的右子树中的一个结点,则 y.key >= x.key 。
code
根据二叉查找树的性质,能够用一个递归算法按排列顺序输出树中的全部关键字。这种算法称为 中序遍历算法 ,由于子树根的关键字在输出时介于左子树和右子树的关键字之间(相似地, 前序遍历 中根的关键字在其左右子树的关键字以前输出,而 后序遍历 中根的关键字在其左右子树的关键字以后输出)。 对象
INORDER-TREE-WALK(x) 1 if x != NIL 2 INORDER-TREE-WALK(x.left) 3 print x.key 4 INORDER-TREE-WALK(x.right)
定理
若是 x 是一棵包含 n 个结点的子树的根,则调用 INORDER-TREE-WALK(x)
过程的时间为 Θ ( n )。
blog
查询二叉查找树
对于二叉查找树,最多见的查找操做除了 SEARCH
外,二叉查找树还支持 MINIMUM
, MAXIMUM
, SUCCESSOR
和 PREDECESSOR
等查询。对高度为 h 的树,它们均可以在 O ( h )时间内完成。 递归
查找
下面的过程在树中查找一个给定的关键字。 it
TREE-SEARCH(x, k) 1 if x == NIL or k == x.key 2 return x 3 if k < x.key 4 return TREE-SEARCH(x.left, k) 5 else 6 return TREE-SEARCH(x.right, k)
该过程从树的根结点开始进行查找,并沿树降低。对碰到的每一个结点 x ,就比较 k 和 x.key 。若是这两个关键字相同,则查找结束。若是 k 小于 x.key ,则继续查找 x 的左子树,若是 k 大于 x.key ,则继续在 x 的右子树中查找。也能够用 while
循环来替代递归过程。table
ITERATIVE-TREE-SEARCH(x, k) 1 while x != NIL and k != x.key 2 if k < x.key 3 x = x.left 4 else 5 x = x.right 6 return x
最大关键字元素和最小关键字元素
要查找二叉树中具备最小关键字的元素,只要从根结点开始,沿着各结点的 left 指针查找下去,直到遇到 NIL
时为止。class
TREE-MINIMUM(x) 1 while x.left != NIL 2 x = x.left 3 return x
在以 x 为根的子树中,最小关键字能够在 x.left 为根的左子树中找到。过程 TREE-MAXIMUM
的伪码是对称的:
TREE-MAXIMUM(x) 1 while x.right != NIL 2 x = x.right 3 return x
对高度为 h 的树,这两个过程的运行时间都是 O ( h )。
前趋和后继
给定一个二叉查找树中的结点,有时要求找出其在中序遍历顺序下它的后继。若是全部的关键字均不相同,则某一结点 x 的后继即具备大于 x.key 中的关键字中最小者的那个结点。
TREE-SUCCESSOR(x) 1 if x.right != NIL 2 return TREE-MINIMUM(x.right) 3 y = x.p 4 while y != NIL and x == y.right 5 x = y 6 y = y.p 7 return y
TREE-SUCCESSOR
代码中包含两种状况。
- 若是结点 x 的右子树非空,则 x 的后继即右子树中的最左结点,能够经过
TREE-MINIMUM(x.right)
过程找到。 - 若是结点 x 的右子树为空,且 x 有一个后继 y ,则 y 是 x 的最低祖先结点,且 y 的左儿子也是 x 的祖先。
以下图所示,包含关键字15的结点的后继是包含关键字17的结点。包含关键字13的结点的后继是包含关键字15的结点。
过程 TREE-PREDECESSOR
和 TREE-SUCCESSOR
对称,其运行时间都是 O ( h )。
TREE-PREDECESSOR(x) 1 if x.left != NIL 2 return TREE-MAXIMUM(x.left) 3 y = x.p 4 while y != NIL and x == y.left 5 x = y 6 y = y.p 7 return y
插入和删除
插入和删除操做会引发二叉树结构上的变化,这时,就要修改其数据结构,以保持二叉查找树性质。
插入
为将一个新值 v 插入到二叉查找树 T 中,能够调用 TREE-INSERT
。传给该过程的参数是个结点 z ,而且有 z.key = v , z.left = NIL
和 z.right = NIL
。
TREE-INSERT(T, z) 1 y = NIL 2 x = T.root 3 while x != NIL 4 y = x 5 if z.key < x.key 6 x = x.left 7 else 8 x = x.right 9 z.p = y 10 if y == NIL 11 T.root = z // tree T was empty 12 elseif z.key < y.key 13 y.left = z 14 else 15 y.right = z
TREE-INSERT
过程从根结点开始,并沿树降低。指针 x 跟踪了这条路径,而 y 始终指向 x 的父结点。过程根据 z.key 与 x.key 的比较结果,决定向左或向右转。这到 x 成为 NIL
为止。这个 NIL
所占位置即咱们想插入项 z 的地方。
删除
将结点 z 从二叉查找树 T 中删除共有以下三种状况,其中一种有一点难懂。
- 若是结点 z 没有孩子结点,咱们就能够直接使用
NIL
来代替该结点。 - 若是结点 z 只有一个子女,能够直接用结点 z 的孩子结点替代 z 。
- 若是结点 z 有两个子女,首先要找到结点 z 的后继 y (它必定在 z 的右子树中),并用 y 替代 z 在树中的位置。
TREE-DELETE
过程用于从二叉查找树 T 中删除一个给定的结点 z ,它按以下四种状况组织代码。
- 若是结点 z 没有左孩子,那么咱们用右孩子替换 z ,其中右孩子可能为
NIL
。右孩子为NIL
即结点 z 没有孩子结点的状况。 - 若是结点 z 有且只有左孩子,那么就用左孩子替代结点 z 。
- 不然,结点 z 有两个子结点。首先找到 z 的后继结点 y ,它位于 z 的右子树且没有左孩子。这里咱们打算用 y 替换 z 。
- 若是 y 是 z 的右孩子,那么直接用 y 替换 z 。
- 不然,用 y 本身的右孩子替换 y ,再用 y 来替换 z 。
此外还有一个子程序 TRANSPLANT
,用于子树之间的替换。
TRANSPLANT(T, u, v) 1 if u.p == NIL 2 T.root = v 3 elseif u == u.p.left 4 u.p.left = v 5 else 6 u.p.right = v 7 if v != NIL 8 v.p = u.p
TREE-DELETE(T, z) 1 if z.left == NIL 2 TRANSPLANT(T, z, z.right) 3 elseif z.right == NIL 4 TRANSPLANT(T, z, z.left) 5 else 6 y = TREE-MINIMUM(z.right) 7 if y.p != z 8 TRANSPLANT(T, y, y.right) 9 y.right = z.right 10 y.right.p = y 11 TRANSPLANT(T, z, y) 12 y.left = z.left 13 y.left.p = y