概述

一颗二叉搜索树可以为空,但如果不为空,必须满足以下性质:

  • 非空左子树的所有键值小于其根结点的键值
  • 非空右子树的所有键值大于其根结点的键值
  • 左、右子树都是二叉搜索树

二叉查找树代码

public class BST<Key extends Comparable<Key>, Value> {
    private Node root;      // 二叉查找树的根结点
    private class Node{
        private Key key;    // 键
        private Value val;  // 值
        private Node left, right;   // 指向子树的链接
        private int size;  // 以该结点为根的子树中的结点总数

        public Node(Key key, Value val, int size) {
            this.key = key;
            this.val = val;
            this.size = size;
        }
    }
    .
    .
    .
}

二叉查找树操作

查找操作

在二叉查找树中查找一个键的递归算法:如果树是空的,则查找未命中;如果被查找的键和根结点的键相等,查找命中,否则我们就(递归地)在适当的子树中继续查找。如果被查找的键较小就选择左子树,较大则选择右子树。

代码

public Value get(Key key) {
    return get(root, key);
}

private Value get(Node x, Key key) {
    // 在以 x 为根结点的子树中查找并返回 key 所对应的值
    if (key == null) throw new IllegalArgumentException("calls get() with a null key");
    if (x == null) return null;
    int cmp = key.compareTo(x.key);
    if      (cmp < 0) return get(x.left, key);
    else if (cmp > 0) return get(x.right, key);
    else              return x.val;
}

插入操作

插入方法的实现逻辑和递归查找很相似:如果树是空的,就返回一个含有该键值对的新结点;如果被查找的键小于根结点的键,我们会继续在左子树中插入该键,否则在右子树中插入该键。

代码

public void put(Key key, Value val) {
    root = put(root, key, val);
}

private Node put(Node x, Key key, Value val) {
    // 如果key存在于为根结点的子树中则更新它的值
    // 否则将以key和val为键值对的新结点插入到该子树中
    if (x == null) return new Node(key, val, 1);
    int cmp = key.compareTo(x.key);
    if      (cmp < 0) x.left  = put(x.left,  key, val);
    else if (cmp > 0) x.right = put(x.right, key, val);
    else              x.val   = val;
    x.size = 1 + size(x.left) + size(x.right);
    return x;
}

查找最小值和最大值

最小元素一定是在树的最左分支的端结点上;最大元素一定是在树的最右分支的端结点上。

// 最小值查找
public Key min() {
    return min(root).key;
} 

private Node min(Node x) { 
    if (x.left == null) return x; 
    else     return min(x.left); 
}

// 最大值查找
public Key max() {
    return max(root).key;
} 

private Node max(Node x) {
    if (x.right == null) return x; 
    else      return max(x.right); 
}

删除操作

deleteMin() 删除最小键

二叉查找树最难实现的方法就是 delete() 方法。作为热身运动,我们先考虑 deleteMin() 方法。最小键只能在左子树的端结点上,所以删除最小键 deleteMin 仅有以下两种情况。如下图所示,最小键就是 15 这个结点,它有两种情况:15 下面无子结点,15 下面有一个子结点(右子结点)。

这两种情况的删除操作是非常容易实现的:若被删除结点无子结点,仅需将其置为 NULL 即可;若被删除的结点有一个子结点,仅需将其父结点指向其子结点即可,此时已经没有任何链接指向被删除的结点,因此它会被垃圾收集器清理掉。

代码

public void deleteMin() {
    root = deleteMin(root);
}

private Node deleteMin(Node x) {
    if (x.left == null) return x.right;
    x.left = deleteMin(x.left);
    x.size = size(x.left) + size(x.right) + 1;
    return x;
}

注意:deleteMax 和 deleteMin 是同理,所以就不再说明了。

delete() 删除操作

有了 deleteMin 操作的启示,我们可以用类似的方式删除任意只有一个子结点(或没有子结点)的结点,但应该怎样删除一个拥有两个子结点的结点呢?如下图所示。

思路如下:

  • 先找到被删除结点 t,再找 t 的右子树中最小结点或左子树最大结点 x,此案例是找右子树最小结点,用 min 实现
  • 将 x 与 t 调换,t 位置就被 x 代替了,并仅需删除 x 原位置即可。这么做的好处是,由删除 t,变成了删除 x,而 x 是最小结点,所以仅一个或无子结点,此时删除操作就简单了,deleteMin 实现即可。
public void delete(Key key) {
    root = delete(root, key);
}

private Node delete(Node x, Key key) {
    if (x == null) return null;

    int cmp = key.compareTo(x.key);
    if      (cmp < 0) x.left  = delete(x.left,  key);
    else if (cmp > 0) x.right = delete(x.right, key);
    else { 
        if (x.right == null) return x.left;  // 一个或无子结点情况
        if (x.left  == null) return x.right;  // 一个或无子结点情况

        // 两个子结点情况
        Node t = x;
        x = min(t.right);
        x.right = deleteMin(t.right);
        x.left = t.left;
    } 
    x.size = size(x.left) + size(x.right) + 1; // 更新结点计数器
    return x;
}

results matching ""

    No results matching ""