二叉搜索树

二叉搜索树

定义

树的概念这里不做说明,在假设我们都知道二叉树的情况下,那么二叉查找树就是符合以下情况的一种特殊的二叉树:对于二叉查找树的某个节点X,他的左子树中所有的节点值都小于X节点的值,右子树所有节点的值都大于X节点的值。例如下面这颗树,就是一个二叉搜索树。该树结构中,16左边的节点都小于16,16右边的节点大于16,其他节点同理。那下面我们分别讨论二叉查找树的增删改查几个重要方法。
在这里插入图片描述

二叉查找树的API

查找

因为二叉搜索树的特点,所以我们查找一个节点是不是在二叉搜索树中,一般有两步,以上面的二叉搜索树为例,查找元素9:

  1. 访问根节点,如果根节点的值正好和查询的元素相等,那么直接返回,这里9不等于16,接续第二步。
  2. 判断9是否大于16,是则从16的右边开始查,否则,从16的左边查,这里9小于16,从16的左边查,定位到8,发现9大于8,从8的右边查,找到9。很显然,二叉搜索树的查询时间复杂度,是优于链表的。下面给出代码实现。
/**
 * 以node为根的二叉搜索树是否包含元素e
 * @param node
 * @param e
 * @return
 */
private boolean contains(Node node, E e) {
    // 递归终止条件
    if (null == node) {
        return false;
    }

    if (e.compareTo(node.e) == 0) {
        return true;
    } else if (e.compareTo(node.e) < 0) {
        return contains(node.left, e);
    } else  {
        return contains(node.right, e);
    }
}

插入

了解了查询的过程,插入就是可以用contains的方法遍历树,如果找到对应的节点,在做一次更新,否则,就插入到遍历的路径上的最后一个空节点。下面给出代码。

/**
 * 递归添加元素
 * @param node
 * @param e
 * @return 返回插入新节后二叉搜索树的根
 */
private Node add(Node node, E e) {
    if (null == node) {
        size ++;
        return new Node(e);
    }

    if (e.compareTo(node.e) < 0) {
        // 添加返回值将new出来的节点挂载到树上
        node.left = add(node.left, e);
    } else if (e.compareTo(node.e) > 0) {
        node.right = add(node.right, e);
    }

    return node;
}

删除

删除相对于增加和查找元素,比较复杂,因为要考虑到这样几种情况。

  • case1 以下图为例,删除元素30,那么我们直接删除30就可以
    在这里插入图片描述
  • case2:删除元素8,这个时候8没有左孩子,所以删除元素8以后,我们可以直接让8的右孩子替代8,成为16的左孩子。
    在这里插入图片描述
  • case3:还是删除元素8,这个时候8没有右孩子,那么我们可以让8的左孩子替换8成为16的左孩子。
    在这里插入图片描述
  • case4:还是删除元素8,这次8既有左孩子,也有右孩子。这种情况我们一般的策略是找到元素8右孩子中的最小的元素,也就是9,让他替代8的位置
    在这里插入图片描述
 /**
     * 删除指定元素
     * @param node 节点
     * @param e 待删除的元素
     * @return 删除后的根节点
     */
    private Node remove(Node node, E e) {
        if (null == node) {
            return null;
        }

        if (e.compareTo(node.e) < 0) {
            node.left = remove(node.left, e);
            return node;
        } else if (e.compareTo(node.e) > 0) {
            node.right = remove(node.right, e);
            return node;
        } else {

            // 左子树为空
            if (null == node.left) {
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            }

            // 右子树为空
            if (null == node.right) {
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }

            // 待删除节点左右子树都不为空
            // 找到待删除元素的右子树的最小节点,替换待删除元素
            Node successor = minNum(node.right);
            // successor现在替换了node的位置,就要将successor从原来的位置删除
            successor.right = removeMin(node.right);
            successor.left = node.left;
            node.left = null;
            node.right = null;
            return successor;
        }
    }

其他API

关于二叉搜索树的其他操作,例如深度优先遍历、广度优先遍历、查询最大最小元素等代码,基本都是用了递归的方法。

package com.tree.BST;



import java.util.LinkedList;
import java.util.Queue;

public class BST<E extends Comparable<E>> {

    private class Node {
        E e;
        Node right;
        Node left;

        public Node(E e) {
            this.e = e;
            this.right = null;
            this.left = null;
        }

    }

    private Node root;
    private int size;

    public BST() {
        this.root = null;
        this.size = 0;
    }

    private int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 添加元素
     * @param e
     */
    public void add(E e) {
        root = add(root, e);
    }

    /**
     * 递归添加元素
     * @param node
     * @param e
     * @return 返回插入新节后二叉搜索树的根
     */
    private Node add(Node node, E e) {
        if (null == node) {
            size ++;
            return new Node(e);
        }

        if (e.compareTo(node.e) < 0) {
            // 添加返回值将new出来的节点挂载到树上
            node.left = add(node.left, e);
        } else if (e.compareTo(node.e) > 0) {
            node.right = add(node.right, e);
        }

        return node;
    }

    /**
     * 是否包含元素e
     * @param e
     * @return
     */
    public boolean contains(E e) {
        return contains(root, e);
    }

    /**
     * 以node为根的二叉搜索树是否包含元素e
     * @param node
     * @param e
     * @return
     */
    private boolean contains(Node node, E e) {
        if (null == node) {
            return false;
        }

        if (e.compareTo(node.e) == 0) {
            return true;
        } else if (e.compareTo(node.e) < 0) {
            return contains(node.left, e);
        } else  {
            return contains(node.right, e);
        }
    }

    /**
     * 前序遍历
     */
    public void preOrder() {
        preOrder(root);
    }

    /**
     * 前序遍历 先访问根节点,在访问左右子树, 递归算法
     * @param node
     */
    private void preOrder(Node node) {
        if (null == node) {
            return;
        }
        System.out.println(node.e);
        preOrder(node.left);
        preOrder(node.right);
    }

    /**
     * 中序遍历
     */
    public void inOrder() {
        inOrder(root);
    }

    /**
     * 中序遍历 先访问左子树,在访问根节点和右子树, 递归算法
     * @param node
     */
    private void inOrder(Node node) {
        if (null == node) {
            return;
        }
        inOrder(node.left);
        //Gson gson = new Gson();
        //String json = gson.toJson(node);
        System.out.println(node.e);
        inOrder(node.right);
    }

    /**
     * 后序遍历
     */
    public void postOrder() {
        postOrder(root);
    }

    /**
     * 后序遍历 先访问左子树,在访问右子树和根节点, 递归算法
     * @param node
     */
    private void postOrder(Node node) {
        if (null == node) {
            return;
        }
        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.e);
    }

    /**
     * 层序遍历
     */
    public void levelOrder() {
        if (null == root) {
            return;
        }

        Queue<Node> queue = new LinkedList<>();
        Queue<Node> levelQueue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            Node cur = queue.remove();
            System.out.print(cur.e + " ");
            if (null != cur.left) {
                queue.add(cur.left);
            }
            if (null != cur.right) {
                queue.add(cur.right);
            }
            if(queue.isEmpty()){
                System.out.println();
                queue.addAll(levelQueue);
                levelQueue.clear();
            }
        }
    }

    /**
     * 获取二叉搜索树的最大节点
     */
    public E maxNum() {
        if (size == 0) {
            throw new IllegalArgumentException("BSI is empty");
        }

        return maxNum(root).e;
    }

    /**
     * 递归获取最大元素,从树的右边遍历
     * @param node
     * @return
     */
    private Node maxNum(Node node) {
        if (null == node.right) {
            return node;
        }

        return maxNum(node.right);
    }

    /**
     * 获取二叉搜索树的最小节点
     */
    public E minNum() {
        if (size == 0) {
            throw new IllegalArgumentException("BSI is empty");
        }

        return minNum(root).e;
    }

    /**
     * 递归获取最小元素,从树的左边遍历
     * @param node 根节点
     * @return 以node为根的最小节点
     */
    private Node minNum(Node node) {
        if (null == node.left) {
            return node;
        }

        return minNum(node.left);
    }

    /**
     * 移除最大元素
     * @return 最大元素值
     */
    public E removeMax() {
        E e = maxNum();
        removeMax(root);
        return e;
    }

    /**
     * 移除最大元素
     * @param node 根节点
     * @return 移除后根节点
     */
    private Node removeMax(Node node) {
        // 如果右子树为空,则根节点为最大节点,删除根节点
        if (null == node.right) {
            Node leftNode = node.left;
            node.left = null;
            size --;
            return leftNode;
        }

        node.right = removeMax(node.right);
        return node;
    }

    /**
     * 删除最小元素
     * @return 最小元素的值
     */
    public E removeMin() {
        E e = minNum();
        removeMin(root);
        return e;
    }

    /**
     * 删除最小元素
     * @param node
     * @return 删除最小节点后的新的二叉搜索树
     */
    private Node removeMin(Node node) {
        // 如果左子树为空,则根节点为最小节点,删除根节点
        if (null == node.left) {
            Node rightNode = node.right;
            node.right = null;
            size --;
            return rightNode;
        }

        node.left = removeMin(node.left);
        return node;
    }

    /**
     * 删除指定元素
     * @param e
     */
    public void remove(E e) {
        root = remove(root, e);
    }

    /**
     * 删除指定元素
     * @param node 节点
     * @param e 待删除的元素
     * @return 删除后的根节点
     */
    private Node remove(Node node, E e) {
        if (null == node) {
            return null;
        }

        if (e.compareTo(node.e) < 0) {
            node.left = remove(node.left, e);
            return node;
        } else if (e.compareTo(node.e) > 0) {
            node.right = remove(node.right, e);
            return node;
        } else {

            // 左子树为空
            if (null == node.left) {
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            }

            // 右子树为空
            if (null == node.right) {
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }

            // 待删除节点左右子树都不为空
            // 找到待删除元素的右子树的最小节点,替换待删除元素
            Node successor = minNum(node.right);
            // successor现在替换了node的位置,就要将successor从原来的位置删除
            successor.right = removeMin(node.right);
            successor.left = node.left;
            node.left = null;
            node.right = null;
            return successor;
        }
    }

    /**
     * 查找key的floor值
     * @param key
     * @return
     */
    public E floor(E key) {
        if (key.compareTo(minNum()) < 0) {
            return null;
        }

        return floor(root, key);
    }

    /**
     * 查找key的floor值
     * @param node
     * @param key
     * @return
     */
    private E floor(Node node, E key) {
        if (null == node) {
            return null;
        }

        // 如果key和node值相等,node就是key对应的floor节点
        if (key.compareTo(node.e) == 0) {
            return key;
        }

        // 如果key比node的值小,那么对应的floor节点肯定在node的左子树
        if (key.compareTo(node.e) < 0) {
            return floor(node.left, key);
        }

        // 如果key比node的值大,node有可能是key对应的floor节点,也有可能不是
        E floor = floor(node.right, key);
        if (floor != null) {
            return floor;
        }

        return node.e;
    }

    /**
     * 查找key的ceil值
     * @param key
     * @return
     */
    public E ceil(E key) {
        if (key.compareTo(maxNum()) > 0) {
            return null;
        }

        return ceil(root, key);
    }

    /**
     * 查找key的ceil值
     * @param node
     * @param key
     * @return
     */
    private E ceil(Node node, E key) {
        if (null == node) {
            return null;
        }

        // 如果key和node值相等,node就是key对应的ceil节点
        if (key.compareTo(node.e) == 0) {
            return key;
        }

        // 如果key比node的值大,那么对应的ceil节点肯定在node的右子树
        if (key.compareTo(node.e) > 0) {
            return ceil(node.right, key);
        }

        // 如果key比node的值小,node有可能是key对应的ceil节点,也有可能不是
        E ceil = ceil(node.left, key);
        if (ceil != null) {
            return ceil;
        }

        return node.e;
    }

    /**
     * 求树的深度
     * @return 树的深度
     */
    public int depth() {
        return depth(root);
    }

    /**
     * 求树的深度
     * @return node为节点的书的树的深度
     */
    private int depth(Node node){
        if (null == node) {
            return 0;
        }
        int leftDepth = depth(node.left);
        int rightDepth = depth(node.right);
        return Math.max(leftDepth, rightDepth) + 1;
    }
}

文章代码参考《数据结构与算法分析》和慕课网liuyubobobo老师的算法大师带你玩转数据结构的课程。这里作为学习笔记整理。推荐一篇写的很不错的博客:浅谈算法和数据结构: 七 二叉查找树

github代码仓库:二叉搜索树

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

半__夏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值