BST树操作


出自《算法》

这部分《算法》里面写得很好,里面有一些操作是以前注意到的,特地整理下
涉及到BST的插入、删除

结构定义

    private Node root;             // root of BST

    private class Node {
    
    
        private Key key;           // sorted by key
        private Value val;         // associated data
        private Node left, right;  // left and right subtrees
        private int size;          // number of nodes in subtree

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

书中用BST树实现了SearchTable,所以会有Key, Value的结构,重点关注size,key和val即可

size()

两个size函数,一个是返回整棵树的大小,另一个是返回以x为root的树的大小,后一个size函数很有用

    /**
     * Returns the number of key-value pairs in this symbol table.
     *
     * @return the number of key-value pairs in this symbol table
     */
    public int size() {
    
    
        return size(root);
    }

    // return number of key-value pairs in BST rooted at x
    private int size(Node x) {
    
    
        if (x == null) return 0;
        else return x.size;
    }

get()

既然是SearchTable,就会有get和put函数,下面是get函数
因为是BST,所以可以用性质来查找

    /**
     * Returns the value associated with the given key.
     *
     * @param key the key
     * @return the value associated with the given key if the key is in the symbol table
     * and {@code null} if the key is not in the symbol table
     * @throws IllegalArgumentException if {@code key} is {@code null}
     */
    public Value get(Key key) {
    
    
        return get(root, key);
    }

    private Value get(Node x, Key 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;
    }

put()

然后是put函数,这个操作会影响树的结果,下面是代码

    /**
     * Inserts the specified key-value pair into the symbol table, overwriting the old
     * value with the new value if the symbol table already contains the specified key.
     * Deletes the specified key (and its associated value) from this symbol table
     * if the specified value is {@code null}.
     *
     * @param key the key
     * @param val the value
     * @throws IllegalArgumentException if {@code key} is {@code null}
     */
    public void put(Key key, Value val) {
    
    
        if (key == null) throw new IllegalArgumentException("calls put() with a null key");
        if (val == null) {
    
    
            delete(key);
            return;
        }
        // 更新root
        root = put(root, key, val);
        assert check();
    }

    private Node put(Node x, Key key, Value 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;
    }

第一个put主要是检查用途,最关键的一行是 root = put(root, key, val);,这是调用的入口
然后我们看下面的put函数,它是一个递归调用,最后会返回一个Node,整个函数的用途相当于:给定一颗 root = x 的树,返回在这棵树插入key后的新树
我们看函数里面的内容,还是类似二分查找,如果key小于当前节点x的key,那么应该在x的左子树插入,插入后需要更新x.left所以有x.left = put(x.left, key, val);,大于的情况同理
当我们走到一个空指针的时候,直接返回一个 new Node...即可,退回到上一层,相当于x.left = new Node(key, ...)。(以left为例)
注意到x.size = 1 + size(x.left) + size(x.right);,在插入结束返回上一层时,这一行代码会不断的更新结点的size属性,最终完成所有经过的结点的更新

deleteMin()

给定一棵以x为根节点的树,deleteMin()可以删除这棵树中最小的结点,并返回删除后的树
同样根据这个意思root = deleteMin(root);是调用的入口

    /**
     * Removes the smallest key and associated value from the symbol table.
     *
     * @throws NoSuchElementException if the symbol table is empty
     */
    public void deleteMin() {
    
    
        if (isEmpty()) throw new NoSuchElementException("Symbol table underflow");
        root = deleteMin(root);
        assert check();
    }

    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;
    }

利用BST的性质,我们一直往左走即可,关键是我们看走到底时候的操作 if (x.left == null) return x.right;
说明如果当前节点没有左孩子,那么我们返回这个节点的右指针
返回了能干嘛呢,看这行代码 x.left = deleteMin(x.left);,此时的x是返回之前的x的父节点,我们用father来描述它,father.left = deleteMin(father.left);,那么根据返回实际上这行代码意思是 father.left = father.left.right;,其中father.left = x
注意到我们还有更新size的代码,这个代码和put一样
下面是操作过程的图例
在这里插入图片描述
还有镜像的函数deleteMax(),和上面的操作步骤一样,这里不贴了

min()

找到key最小的结点

    /**
     * Returns the smallest key in the symbol table.
     *
     * @return the smallest key in the symbol table
     * @throws NoSuchElementException if the symbol table is empty
     */
    public Key min() {
    
    
        if (isEmpty()) throw new NoSuchElementException("calls min() with empty symbol table");
        return min(root).key;
    }

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

delete()

重点来了,delete能够删除key为给定值的结点,并且返回删除后的新树

    /**
     * Removes the specified key and its associated value from this symbol table
     * (if the key is in this symbol table).
     *
     * @param key the key
     * @throws IllegalArgumentException if {@code key} is {@code null}
     */
    public void delete(Key key) {
    
    
        if (key == null) throw new IllegalArgumentException("calls delete() with a null key");
        root = delete(root, key);
        assert check();
    }

    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;
    }

同样root = delete(root, key);是调用入口
对于下面的delete方法,我们首先需要找到x.key == key的结点x,寻找过程还是binarySearch

找到之后,如果x结点只有一个孩子,那么我们直接返回这个孩子即可
如果不是,我们做如下操作

  1. 找到x右子树的最小节点,这个节点实际上是x的后继
  2. 我们delete掉右子树的最小节点,然后把x的后继节点指向操作后x的右子树
  3. 更新后继节点的左子树为x的左子树

此时待删除结点已经没有指针指向它,它将被回收,而它的后继节点代替它成为新的节点,并不会破坏树的结构
操作步骤
在这里插入图片描述
剩下的一些代码,不涉及BST结构的改变,大部分都是在二分的基础上改进,这里就不贴了

猜你喜欢

转载自blog.csdn.net/hhmy77/article/details/114089838
今日推荐