二叉搜索树的基本操作及其变体

1. 什么是二叉搜索树

二叉搜索树可以使用一个链表数据结构来表示, 每个结点就是一个对象. 除了 key k e y 和卫星数据(satellite data, 该对象附带的其它数据)之外, 每个结点还包含着属性 left l e f t , right r i g h t p p , 它们分别指向结点的左孩子, 右孩子和双亲. 如果某个孩子结点或父结点不存在, 则该属性的值为NIL. 根结点是树中唯一父结点为NIL的结点.

1.1 二叉搜索树结构图

二叉搜索树结构图

1.2 二叉搜索树性质

  • x是一个二叉搜索树中的一个结点. 如果 y y x左子树中的一个结点, 那么 y.keyx.key y . k e y ≤ x . k e y .如果 y y x右子树中的一个结点, 那么 y.keyx.key y . k e y ≥ x . k e y .

    根据性质, 可以得到:

    • 可以通过递归算法来按序输出二叉搜索树中的所有关键字(称为中序遍历)
    • 如何中序遍历二叉树?
    • 遍历一棵有n个结点的二叉搜索树需要耗费 O O (n)的时间. (每个结点需要遍历两次, 左孩子和右孩子)

    2. 查询二叉搜索树

    2.1 查找

    如在一棵二叉搜索树中查找一个具有给定关键字的结点(结点可以不存在). 输入一个根结点一个关键字 k 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)
    
    //迭代实现为:
    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

    为什么迭代方法的效率比递归高得多?

    递归效率低是函数调用的开销导致的。在一个函数调用之前需要做许多工作,比如准备函数内局部变量使用的空间、搞定函数的参数等等,这些事情每次调用函数都需要做,因此会产生额外开销导致递归效率偏低,所以逻辑上开销一致时递归的额外开销会多一些当然了.

    通过有意识的组织代码的写法可以把某些递归写成尾递归尾递归可以进行特殊的优化所以效率会比普通的递归高一些,也不会因为递归太多导致栈溢出.

    遍历树还不用递归的话,那么人肉写一个栈+深度优先遍历或者人肉队列+广度优先遍历,再辅以黑魔法给栈或者队列提速,应该会比递归快一些,加速幅度和语言和写法相关,但在大多数情况下我觉得是得不偿失的,花了很大精力很可能效率提升不明显.

    —-来自知乎为什么说递归效率低?Yul8ulY的回答

    尾调用(Tail Call)的优化:

    2.2 最大关键字和最小关键字

    通过树根沿着 left l e f t 孩子直到遇到一个NIL, 则当前结点为最小关键字结点

    伪代码实现为:

    TREE-MINIMUM(x)
        while x.left ≠ NIL
            x = x.left
        return x
    

    同理, 可以得出最大关键字结点的求法为:

    TREE-MAXIMUM(x)
        while x.right ≠ NIL
            x = x.right
        return x
    

    2.3 后继和前驱

    • 一个结点的后继是二叉搜索树中序遍历结果中该结点的后一个结点(也就是 key k e y 值比当前 key k e y 值大的第一个结点)
    • 如果结点 x x 的右孩子非空并有一个后继y, 那么 y y 就是x有左节点的最底层祖先, 并且它也是 x x 的一个祖先.

    如下图, 关键字为13的结点的后继是关键词为15的结点.

    这里写图片描述

    伪代码实现为:

    //寻找后继结点
    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-PREDECESSOR(x)
        if x.left ≠ NIL
            return TREE-MAXIMUM(x.left)
        y = x.p
        while y ≠ NIL and x == y.left
            x = y
            y = y.p
        return y

    总结

    • 在一棵高度为h的二叉搜索树上, 动态集合上的操作SEARCH, MINIMUM, MAXIMUM, SUCCESSOR, PREDECESSOR均能在 O O (h)时间内执行完.

    3. 插入和删除

    3.1 插入

    将一个新值 v v 插入到一棵二叉搜索树T中, 需要调用过程TREE-INSERT. 该过程以结点 z z 作为插入, 其中z.key = v v , z.left = NIL, z.right z . r i g h t = NIL. 这个过程要修改 T T z的某些属性, 来把 z z 插入到树中的相应位置上.

    将关键字为13的数据项插入二叉搜索树的过程图解为:

    这里写图片描述

    伪代码实现为:

    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  // tree T was empty
        elseif z.key < y.key
            y.left = z
        else y.right = z

    3.2 删除

    删除结点z的整个策略分为3个基本情况:

    • 如果 z z 没有孩子结点, 则简单删除它, 并修改父结点, 用NIL代替z
    • 如果 z z 只有一个孩子结点, 将z的孩子提升到 z z 的位置, 并修改z的父结点, 用 z z 的孩子代替z. (如下图(a), (b)过程)
    • 如果 z z 有两个孩子结点, 需要寻找z的后继 y y :
      • 如果z的右孩子没有左孩子, 则z的右孩子为后继 y y , 用y代替 z z , 保持y的右子树不变. (如下图中(c)过程)
      • 如果z的右孩子( r r )有左孩子, 则寻找r子树中最小的结点, 即为 y y , 用y的右子树(如果它不是叶子结点)代替 y y 的父结点的左子树, 用y代替 z z , 将r的父结点设置为 y y , 将z的父结点的右子树设置为 y y . (如下图中(d)过程)

    这里写图片描述

    为了在二叉搜索树中移动子树, 定义一个子过程TRANSPLANT, 它是用另一棵子树代替一棵子树.

    TRANSPLANT用一棵以v为子树的根来代替一棵以 u u 为根的子树时, 伪代码实现如下:

    TRANSPLANT(T, u, v)
        if u.p == NIL
            T.root = v
        elseif u == u.p.left
            u.p.left = v
        else 
            u.p.right = v
        if v ≠ NIL
            v.p = u.p

    注意:

    • 以上TRANSPLANT并没有处理v.left v.right v . r i g h t 的更新, 这些更新都有TRANSPLANT的调用者来负责

    二叉搜索树 T T 中删除结点z的删除过程为:

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

    4. 随机构造二叉搜索树

    5. 二叉搜索树的变体

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值