查找:二叉查找树总结

二叉树查找树

一棵二叉查找树(BST)是一棵二叉树,其中每个节点都含有一个Comparable的键(以及相关的值),且每个节点的键都大于其左子树中的任意节点的键key,而小于右子树的任意节点的键key。

节点

每个节点包含一个键、一个值、一个左链接、一个右链接、一个节点计数器N
每个节点包含结点数:
size(x) = size(x.left) + size(x.right) + 1;

    private class Node {
        private Key key; // 键
        private Value val; // 值
        private Node left; // 左子树链接
        private Node right; // 右子树链接
        private int N; // 以该节点为根的子树中的节点总数

        public Node(Key key, Value val, int N) {
            this.key = key;
            this.val = val;
            this.N = N;
        }
    }
    // 返回节点的个数
    public int size() {
        return size(root);
    }

    private int size(Node x) {
        if (x == null)
            return 0;
        else
            return x.N;
    }

根据key获取value值(也可改为contains方法,是否包含某key)

思想:
如果树为空,则查找未命中;如果被查找的键和根节点的键相等,查找命中;否则递归的在左右子树中继续查找。被查找的键较小,选择左子树;较大选择右子树。

    /**
     * 根据key 获取val
     * 
     * @param key
     * @return
     */
    public Value get(Key key) {
        return get(root, key);
    }

    private Value get(Node x, Key key) {
        // 以x为根节点的子树中查找,并返回key对应的值
        // 如果找不到则返回null
        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;
    }

插入键值对

    /**
     * 插入键值对
     * 
     * 注意需要修改结点计数器N
     * 
     * @param key
     * @param val
     */
    public void put(Key key, Value val) {
        // 查找key,找到更新它的值,否则为它创建一个新的节点
        root = put(root, key, val);
    }

    public Node put(Node x, Key key, Value val) {
        // 如果key存在于以x为根节点的子树中,则更新它的值
        // 否则以key、value键值对的新节点插入到该子树中
        if (x == null)
            return new Node(key, val, 1); // 如果key不存在,插入新的键值对
        int cmp = key.compareTo(x.key); // 查找插入位置,若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.N = size(x.left) + size(x.right) + 1;
        return x;
    }

查找最大键和最小键key

分析:
找到最左或最右的节点即可

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

    private Node min(Node x) {
        // 若为空,即根节点
        // 若不为空,找到左子树的最左键
        if (x.left == null)
            return x;
        else
            return min(x.left);
    }

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

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

查找小于某key的最大键,大于某key的最小键

分析:以小于某key的最大键为例
如果给定键key小于二叉树根节点的键,那么小于key的最大键floor(key)一定在根节点的左子树中;
如果给定的键key大于二叉树的根节点,那么当根节点右子树中存在小于等于key的节点时,小于等于key的最大键floor(key)才会出现在右子树;否则根节点就floor(key)。

    /**
     * 查找小于key的最大键
     * 
     * @param key
     * @return
     */
    public Key floor(Key key) {
        Node x = floor(root, key);
        if (x == null)
            return null;
        return x.key;
    }

    private Node floor(Node x, Key key) {
        if (x == null)
            return null;
        int cmp = key.compareTo(x.key);
        if (cmp == 0) // 若x的key等于key
            return x;
        if (cmp < 0) // 若key小于x.key,需在左子树递归查找
            return floor(x.left, key);

        Node t = floor(x.right, key);
        if (t != null)
            return t;
        else
            return x; // 根结点小于右子树所有值
    }

    /**
     * 查找大于key的最小键
     */
    public Key ceiling(Key key) {
        Node x = ceiling(root, key);
        if (x == null)
            return null;
        return x.key;
    }

    private Node ceiling(Node x, Key key) {
        if (x == null)
            return null;
        int cmp = key.compareTo(x.key);
        if (cmp == 0)
            return x;
        if (cmp > 0)
            return ceiling(x.right, key);

        Node t = ceiling(x.left, key);
        if (t != null)
            return t;
        else
            return x;
    }

找出排名为k的键(树中有k个小于k的键,排名开始于0)

分析:
如果左子树中结点数t大于k,继续递归在左子树查找。
t = k,返回根结点中的键
t < k, 递归在右子树查找查找排名为(k-t-1)的键。

    /**
     * 找出排名为k的键 --树中有k个小于k的键
     * 
     * 排名为k,是第k+1个
     */
    // 如果左子树中结点数t大于k,继续递归在左子树查找。
    // t=k,返回根结点中的键
    // t<k, 递归在右子树查找查找排名为(k-t-1)的键。
    public Key select(int k) {
        return select(root, k).key;
    }

    private Node select(Node x, int k) {
        // 返回排名为k的节点
        if (x == null)
            return null;
        int t = size(x.left);
        if (t > k)
            return select(x.left, k);
        else if (t < k)
            return select(x.right, k - t - 1);
        else
            return x;
    }

给定键,返回排名

如果给定键和根节点键相等,返回左子树中的节点总数t;
如果给定键小于根节点,返回该键在左子树中的排名(递归)
等于根节点,返回t+1(根节点)加上它在右子树中的排名

    /**
     * 返回给定键的排名
     */
    public int rank(Key key) {
        return rank(key, root);
    }

    private int rank(Key key, Node x) {
        // 返回以x为根节点的子树中小于x.key的键的数量
        if (x == null)
            return 0;
        int cmp = key.compareTo(x.key);
        if (cmp < 0)
            return rank(key, x.left);
        else if (cmp > 0)
            return 1 + size(x.left) + rank(key, x.right);
        else
            return size(x.left);
    }

删除最小键、最大键、删除

删除:
将指向即将被删除的节点的链接保存为t
将x指向它的后继节点min(t.right)
将x的右链接(原本指向一棵所有节点都大于x.key的二叉查找树)指向deleteMin(t.right), 也就是删除后所有节点仍然大于x.key的子二叉查找树
将x的左链接 设为t.left(其下所有的键都小于被删除的节点和它的后继节点)

    /**
     * 删除最小键
     */
    public void deleteMin() {
        root = deleteMin(root);
    }

    public Node deleteMin(Node x) {
        if (x.left == null)
            return x.right; // 无左节点,删除根节点
        x.left = deleteMin(x.left);
        x.N = size(x.left) + size(x.right) + 1;
        return x;
    }

    /**
     * 删除最大键
     */
    public void deleteMax() {
        root = deleteMax(root);
    }

    public Node deleteMax(Node x) {
        if (x.right == null)
            return x.left; // 无右节点,删除根节点
        x.right = deleteMax(x.right);
        x.N = size(x.left) + size(x.right) + 1;
        return x;
    }

    /**
     * 删除
     */
    public void delete(Key key) {
        root = delete(root, key);
    }

    public 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 { // 找到key了
            if (x.right == null)
                return x.left;
            if (x.left == null)
                return x.right;
            Node t = x;
            x = min(t.right); // x指向后继节点:右子树的最小节点
            x.right = deleteMin(t.right); // 新节点的右连接为删除了原删除了最小节点的右子树
            x.left = t.left;
        }
        x.N = size(x.left) + size(x.right) + 1;
        return x;
    }

范围查找

参照中序遍历

    /**
     * 范围查找
     * 
     * 使用foreach遍历所有键---对照中序遍历算法理解
     */
    public Iterable<Key> keys() {
        return keys(min(), max());
    }

    public Iterable<Key> keys(Key lo, Key hi) {
        Queue<Key> queue = new Queue<>(); // 使用队列收集符合条件的键
        keys(root, queue, lo, hi);
        return queue;
    }

    private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
        if (x == null)
            return;
        int cmplo = lo.compareTo(x.key); // [lo, hi]
        int cmphi = hi.compareTo(x.key);
        if (cmplo < 0)
            keys(x.left, queue, lo, hi); // 递归查找根节点左子树
        if (cmplo <= 0 && cmphi >= 0)
            queue.enqueue(x.key); // 查找根节点
        if (cmphi > 0)
            keys(x.right, queue, lo, hi); // 递归查找根节点的右子树
    }

遍历:先序、中序、后序

    /**
     * 二叉树遍历算法:先序、中序、后序
     */
    // 中序遍历--左根右
    public void print(Node x) {
        if (x == null)
            return;
        print(x.left);
        System.out.println(x.key);
        print(x.right);
    }

    // 先序遍历--根左右
    public void preprint(Node x) {
        if (x == null)
            return;
        System.out.println(x.key);
        preprint(x.left);
        preprint(x.right);
    }

    // 后序遍历--左右根
    public void postprint(Node x) {
        if (x == null)
            return;
        postprint(x.left);
        postprint(x.right);
        System.out.println(x.key);
    }

二叉树的高度

    /**
     * 二叉树高度
     * 
     * 特别注意:叶子节点高度为0,根节点的深度为0
     */
    public int height() {
        return height(root);
    }

    private int height(Node x) {
        if (x == null)
            return -1;
        return 1 + Math.max(height(x.left), height(x.right));
    }

完整表示

public class BST<Key extends Comparable<Key>, Value> {
    private Node root; // 二叉查找树的根节点

    private class Node {
        private Key key; // 键
        private Value val; // 值
        private Node left; // 左子树链接
        private Node right; // 右子树链接
        private int N; // 以该节点为跟的子树中的节点总数

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

    // 返回节点的个数
    public int size() {
        return size(root);
    }

    private int size(Node x) {
        if (x == null)
            return 0;
        else
            return x.N;
    }

    /**
     * 根据key 获取val
     * 
     * @param key
     * @return
     */
    public Value get(Key key) {
        return get(root, key);
    }

    private Value get(Node x, Key key) {
        // 以x为根节点的子树中查找,并返回key对应的值
        // 如果找不到则返回null
        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;
    }

    /**
     * 插入键值对
     * 
     * 注意需要修改结点计数器N
     * 
     * @param key
     * @param val
     */
    public void put(Key key, Value val) {
        // 查找key,找到更新它的值,否则为它创建一个新的节点
        root = put(root, key, val);
    }

    public Node put(Node x, Key key, Value val) {
        // 如果key存在于以x为根节点的子树中,则更新它的值
        // 否则以key、value键值对的新节点插入到该子树中
        if (x == null)
            return new Node(key, val, 1); // 如果key不存在,插入新的键值对
        int cmp = key.compareTo(x.key); // 查找插入位置,若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.N = size(x.left) + size(x.right) + 1;
        return x;
    }

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

    private Node min(Node x) {
        // 若为空,即根节点
        // 若不为空,找到左子树的最左键
        if (x.left == null)
            return x;
        else
            return min(x.left);
    }

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

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

    /**
     * 查找小于key的最大键
     * 
     * @param key
     * @return
     */
    public Key floor(Key key) {
        Node x = floor(root, key);
        if (x == null)
            return null;
        return x.key;
    }

    private Node floor(Node x, Key key) {
        if (x == null)
            return null;
        int cmp = key.compareTo(x.key);
        if (cmp == 0) // 若x的key等于key
            return x;
        if (cmp < 0) // 若key小于x.key,需在左子树递归查找
            return floor(x.left, key);

        Node t = floor(x.right, key);
        if (t != null)
            return t;
        else
            return x; // 根结点小于右子树所有值
    }

    /**
     * 查找大于key的最小键
     */
    public Key ceiling(Key key) {
        Node x = ceiling(root, key);
        if (x == null)
            return null;
        return x.key;
    }

    private Node ceiling(Node x, Key key) {
        if (x == null)
            return null;
        int cmp = key.compareTo(x.key);
        if (cmp == 0)
            return x;
        if (cmp > 0)
            return ceiling(x.right, key);

        Node t = ceiling(x.left, key);
        if (t != null)
            return t;
        else
            return x;
    }

    /**
     * 找出排名为k的键 --树中有k个小于k的键
     * 
     * 排名为k,是第k+1个
     */
    // 如果左子树中结点数t大于k,继续递归在左子树查找。
    // t=k,返回根结点中的键
    // t<k, 递归在右子树查找查找排名为(k-t-1)的键。
    public Key select(int k) {
        return select(root, k).key;
    }

    private Node select(Node x, int k) {
        // 返回排名为k的节点
        if (x == null)
            return null;
        int t = size(x.left);
        if (t > k)
            return select(x.left, k);
        else if (t < k)
            return select(x.right, k - t - 1);
        else
            return x;
    }

    /**
     * 返回给定键的排名
     */
    public int rank(Key key) {
        return rank(key, root);
    }

    private int rank(Key key, Node x) {
        // 返回以x为根节点的子树中小于x.key的键的数量
        if (x == null)
            return 0;
        int cmp = key.compareTo(x.key);
        if (cmp < 0)
            return rank(key, x.left);
        else if (cmp > 0)
            return 1 + size(x.left) + rank(key, x.right);
        else
            return size(x.left);
    }

    /**
     * 删除最小键
     */
    public void deleteMin() {
        root = deleteMin(root);
    }

    public Node deleteMin(Node x) {
        if (x.left == null)
            return x.right; // 无左节点,删除根节点
        x.left = deleteMin(x.left);
        x.N = size(x.left) + size(x.right) + 1;
        return x;
    }

    /**
     * 删除最大键
     */
    public void deleteMax() {
        root = deleteMax(root);
    }

    public Node deleteMax(Node x) {
        if (x.right == null)
            return x.left; // 无右节点,删除根节点
        x.right = deleteMax(x.right);
        x.N = size(x.left) + size(x.right) + 1;
        return x;
    }

    /**
     * 删除
     */
    public void delete(Key key) {
        root = delete(root, key);
    }

    public 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 { // 找到key了
            if (x.right == null)
                return x.left;
            if (x.left == null)
                return x.right;
            Node t = x;
            x = min(t.right); // x指向后继节点:右子树的最小节点
            x.right = deleteMin(t.right); // 新节点的右连接为删除了原删除了最小节点的右子树
            x.left = t.left;
        }
        x.N = size(x.left) + size(x.right) + 1;
        return x;
    }

    /**
     * 范围查找
     * 
     * 使用foreach遍历所有键---对照中序遍历算法理解
     */
    public Iterable<Key> keys() {
        return keys(min(), max());
    }

    public Iterable<Key> keys(Key lo, Key hi) {
        Queue<Key> queue = new Queue<>(); // 使用队列收集符合条件的键
        keys(root, queue, lo, hi);
        return queue;
    }

    private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
        if (x == null)
            return;
        int cmplo = lo.compareTo(x.key); // [lo, hi]
        int cmphi = hi.compareTo(x.key);
        if (cmplo < 0)
            keys(x.left, queue, lo, hi); // 递归查找根节点左子树
        if (cmplo <= 0 && cmphi >= 0)
            queue.enqueue(x.key); // 查找根节点
        if (cmphi > 0)
            keys(x.right, queue, lo, hi); // 递归查找根节点的右子树
    }

    /**
     * 二叉树遍历算法:先序、中序、后序
     */
    // 中序遍历
    public void print(Node x) {
        if (x == null)
            return;
        print(x.left);
        System.out.println(x.key);
        print(x.right);
    }

    // 先序遍历
    public void preprint(Node x) {
        if (x == null)
            return;
        System.out.println(x.key);
        preprint(x.left);
        preprint(x.right);
    }

    // 后序遍历
    public void postprint(Node x) {
        if (x == null)
            return;
        postprint(x.left);
        postprint(x.right);
        System.out.println(x.key);
    }

    /**
     * 二叉树高度
     * 
     * 特别注意:叶子节点高度为0,根节点的深度为0
     */
    public int height() {
        return height(root);
    }

    private int height(Node x) {
        if (x == null)
            return -1;
        return 1 + Math.max(height(x.left), height(x.right));
    }
}

#

主要参考来源于《算法》第四版
https://github.com/aistrate/AlgorithmsSedgewick/blob/master/3-Searching/3-2-BinarySearchTrees/BST.java

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值