第12 章 二叉搜索树

本文详细介绍了二叉搜索树的定义、性质,包括如何进行查询、插入和删除操作。在二叉搜索树中,查找、最小值、最大值等操作的时间复杂度为O(h),其中h为树的高度。此外,文章还探讨了随机构建二叉搜索树的期望高度为O(lgn)。

  搜索树结构支持许多动态集合操作,因此,使用一棵搜索树既可以作为一个字典又可以作为一个优先队列。
  二叉搜索树的基本操作所花费的时间与这棵树的高度成正比。对于 n 个节点的一个完全二叉树来说,这些操作的最坏运行时间为θ(lgn)。然而,如果这棵树是一条 n 个节点组成的线性链,那么同样的操作就要花费θ(n)的最坏运行时间。
  

12.1 什么是二叉搜索树

  一颗二叉搜索树是以一颗二叉树来组织的,这样一棵树可以用一个链表数据结构来表示,其中每个节点就是一个对象。除了 key 和卫星数据之外,每个节点还包含属性left 、right、和p。如果某个孩子结点和父结点不存在,则相应属性的值为NIL。跟节点是树中唯一父指针为NIL的结点。
  二叉搜索树:对任何节点 x,其左子树(并不是左孩子)中的关键字最大不超过x.key,其右子树中的关键字最小不低于x.key。不同的二叉树可以代表同一组值的集合。
  二叉搜索树按序输出树中的所有关键字的递归算法:中序遍历算法。这样命名的原因是输出的子树根的关键字位于其左子树的关键字值和右子树的关键字值之间。(类似地,先序遍历中输出的根的关键字在其左右子树的关键字值之前,而后序遍历输出的根的关键字在其左右子树的关键字值之后)。
INORDER-TREE-WALK(T.root)

if x != NIL
    INORDER-TREE-WALK(x.left)
    print x.key
    INORDER-TREE-WALK(x.right)

定理 12.1 如果 x 是一颗有 n 个结点子树的根,那么调用INORDER-TREE-WALK(x)需要θ(n)时间

12.2 查询二叉搜索数

查找
  在一颗二叉搜索树中查找一个具有给定关键字的结点。输入一个指向树根的指针和一个关键字 k。存在,返回一个指向关键字为 k 的结点的指针;否则返回NIL。
TREE-SEARCH(x, k)

if x == NIL or k == x.key
    return x
if k < x.key
    return TREE-SEARCH(x.left, k)
else return TREE-SEARCH(x.right, k)

TREE-SEACH的运行时间为O(h),其中 h 为这颗数的高度。
同样可以采用while循环来展开递归,用一种迭代方式重写这个过程。对于大多数计算机,迭代版本的效率要高的多。
ITERATIVE-TREE-SEARCH(x, k)

while x!= NIL and k != x.key
    if k < x.key
        x = x.left
    else x = x.right
return x

最大关键字和最小关键字元素
TREE-MINIMUM(x)

while x.left != NIL
    x = x.left
return x

TREE-MAXIMUM(x)

while x.right != NIL
    x = x.right
return x

这两个过程在一颗高度为 h 的树上均能在O(h)时间内完成。
后继和前驱
TREE-SUCCESSOR(x)(后继)

if x.right != NIL
    return TREE-MINIMUM(x.right)
y = x.p
while y!= NIL and x == y.right
    x = y
    y = y.p
return y

TREE-SUCCESSOR的运行时间为O(h)。
定理12.2 在一颗高度为 h 的二叉树搜索树上,动态集合上的操作SEARCH、MINIMUN、MAXIMUN、SUCCESSOR和PREDECESSOR可以在O(h)时间内完成

12. 3 插入和删除

插入一个新结点带来的树修改要相对简单些,而删除的处理有些复杂。
插入
将一个新值 v插入到一个二叉搜索数T中,调用TREE-INSERT,该过程以结点z作为输入,其中z.key = v,z.left = NIL,z.right = NIL。
TREE-INSERT(T, z)

y = NIL
x = T.root
while x != NIL
    y = x
    if z.key < x.key
     x = x.left
    else x = x.right
z.p = y
if y == NIL
    T.root = z
else if z.key < y.key
    y.left = z
else y.right = z

TREE-INSERT从树根开始,指针 x 记录一条向下的简单路径,并查找替换的输入项 z 的NIL。该过程保持遍历指针 y 作为 x 的双亲。初始化后,第 3~7 行的while循环使得这两个指针沿树向下移动,向左或向右取决于z.key和x.key的比较,知道x变为NIL。这个NIL占据的位置就是输入项 z要放置的地方。需要遍历指针 y,因为找到 NIL时要知道z属于那个节点。第 8~13 行设置相应的指针,使得 z 插入其中。
与其他搜索数的原始操作一样,过程TREE-INSERT在一颗高度为h的树上的运行时间为O(h)。
删除
  从一颗二叉搜索树 T 中删除一个结点 z 的整个策略分为三种基本情况,但只有一种情况棘手:
  

  • 如果 z 没有孩子结点,那么只是简单的将它删除,并修改它的父节点,用NIL作为孩子来替换 z。
  • 如果z只有一个孩子,那么将这个孩子提升到树 中 z 的位置上,并修改 z 的父结点,用 z 的孩子来替换 z。
  • 如果 z 有两个孩子,那么找 z 的后继 y(一定在 z 的右子树中),并让 y 占据树中 z 的位置。 z的原来右子树部分成为 y 的新的右子树,并且 z 的左子树成为 y 的新左子树。

      从一颗二叉搜索树 T 中删除一个给定的结点 z,这个过程取指向 T 和 z的指针作为输入参数。它与前面概括出的三种情况有些不同。
      

  • 如果 z 没有左孩子,那么用其右孩子来替换 z,这个右孩子可以是 NIL,也可以不是。当 z 的右孩子是 NIL时,此时这种情况归为 z 没有孩子结点的情形。当 z 的右孩子非 NIL时,这种情况就是 z 仅有一个孩子节点的情形,该孩子就是其右孩子。
  • 如果 z 仅有一个孩子且为其左孩子,那么用其左孩子来替换 z。
  • 否则, z既有一个左孩子又有一个右孩子,要查找 z 的后继 y,这个后继位与 z 的右子树中并且没有左孩子。需要将 y 移除原来的位置进行拼接,并替换树中的z。
  • 如果 y 是 z 的右孩子,那么用 y 替换 z ,并仅留下 y 的右孩子。
  • 否则,y 位于 z 的右子树中当并不是 z 的右孩子。在这种情况下,先用 y 的右孩子替换 y,然后再用 y 替换 z。
    为了在二叉搜索树中,定义一个子过程TRANSPLANT,它是用另一颗子树替换一颗子树并成为其双亲的孩子结点。但TRANSPLANT用一颗以 v 为根的子树替换一颗以 u 为根的子树时,结点 u 的双亲就变为 v 的双亲,并且最后 v成为 u 的双亲的相应孩子。
    TRANSPLANT(T, u, v)
if u.p == NIL
    T.root = v
else if u == u.p. left
    u.p.left = v
else u.p.right = v
if v != NIL
    v.p = u.p

下面是从二叉搜索树中删除结点 z 的删除过程:
TREE-DELETE(T, z)

if z.left == NIL
    TRANSPLANT(T, z, z.right)
else if z.right == NIL
    TRANSPLANT(T, z, z.left)
else y = TREE-MINIMUM(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

TREE-DELETE的花费时间为O(h)。
定理12.3 在一颗高度为h 的二叉搜索树上,实现动态集合操作INSERT和DELETE的运行时间为O(h)。

12.4 随机构建二叉搜索树

  二叉搜索树的高度h>= lgn.我们定义 n 个关键字的一颗随机构建二叉搜索树为按随机次序插入这些关键字到一颗初始为空树中而生成的数。
定理 12.4 一颗有 n 个不同关键字的随机构建二叉搜索树的期望高度为O(lgn)。

最优二叉搜索树(Optimal Binary Search Tree, OBST)是一种特殊的二叉搜索树,它通过优化节点的插入顺序来最小化搜索操作的平均成本。在最优二叉搜索树中,每个节点被访问的概率不同,因此我们的目标是根据这些概率来安排节点,使得整体的搜索成本最低。 在Python中实现最优二叉搜索树通常需要使用动态规划的方法。以下是一个简单的步骤介绍: 1. **定义问题**: 给定一组键和它们对应的访问概率,我们需要构建一棵最优二叉搜索树。 2. **初始化**: 创建一个二维数组`cost[i][j]`,其中`cost[i][j]`表示从第`i`个键到第`j`个键的子树的最小平均搜索成本。 3. **填充表格**: 使用动态规划的方法填充这个表格。对于每一个可能的子树范围(即所有可能的`i`和`j`),计算包含在这个范围内的所有键作为根节点时的成本,并选择最小的成本。 4. **构建树**: 根据计算出的最小成本表,递归地构建最优二叉搜索树。 下面是一个简单的Python代码示例,展示了如何实现这一过程: ```python def optimal_bst(keys, freq, n): # 创建成本矩阵 cost = [[0 for x in range(n)] for y in range(n)] # 初始化对角线为频率 for i in range(n): cost[i][i] = freq[i] # 构建成本矩阵 for L in range(2, n+1): # L是当前考虑的链长度 for i in range(n-L+1): j = i + L - 1 cost[i][j] = float('inf') for r in range(i, j+1): c = 0 if r > i: c += cost[i][r-1] if r < j: c += cost[r+1][j] c += sum(freq[i:j+1]) if c < cost[i][j]: cost[i][j] = c return cost[0][n-1] # 示例 keys = [10, 12, 20] freq = [34, 8, 50] n = len(keys) print("Cost of Optimal BST is", optimal_bst(keys, freq, n)) ``` 这段代码首先定义了一个函数`optimal_bst`来计算最优二叉搜索树的成本。然后,它使用一个示例键集和相应的频率来调用这个函数,并打印出最优二叉搜索树的成本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值