java——二分搜索树

二分搜索树的介绍:

①二分搜索树是一棵二叉树
②动态数据结构
③二分搜索树中每一个结点都需要满足:左子树的所有的结点的值均小于该结点, 右子树的所有的结点的值均大于该结点
④因为二分搜索树中有严格的位置要求, 故存储的数据类型必须具有可比性, 可以通过实现Compaerable接口来要求存储类型必须具有可比较性
⑤每一棵子数也是一个二分搜索树
二叉树具有天然的递归结构, 二分搜索树同理
⑦树中可以包含重复元素, 也可以不包含重复元素, 取决于在存储时对其与已有元素的判断条件, 本篇文章引入一个新的变量frequency用于表示该元素出现的次数

二分搜索树样例:

二分搜索树的实现:

确定类型及类型实现方式:

二分搜索树的存储类型是结点, 每一棵二分搜索树都有一个根节点, 故可以通过使用内部类来实现一个描述结点的类

代码演示:
// <V extends Comparable<V>>表示存储类型必须实现Comparable接口, 同时还需要让该类型是该接口泛型的类型或其子类
public class BinarySearchTree<V extends Comparable<V>> {
    private Node<V> root;// 二分搜索树的根节点
    private int size;// 保存存储结点的个数, 重复的变量只计入一次

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

    // 内部类
    private static class Node<V> {
        V data;
        int frequency;// 频率
        Node<V> left;// 左孩子
        Node<V> right;// 右孩子

        public Node() {
        }

        public Node(V data) {
            this.data = data;
            frequency = 1;
        }
    }
}

二分搜索树添加结点:

在添加结点时, 先判断当前结点是否为空, 如果为空则创建一个结点将该结点添加至当前位置; 如果不为空, 比较待添加元素与当前结点元素的值的大小, 如果待添加元素小于当前结点元素, 向当前结点的左孩子继续寻找空位置, 如果如果待添加元素大于当前结点元素, 向当前结点的右孩子继续寻找空位置. 添加元素时重复以上操作即可完成添加. 那么就可以使用递归去实现

代码演示:
    // 添加元素, 重复元素使用频率去记数, 一棵树上同一个元素只记录一次
    public void add(V data) {
        this.root = add(this.root, data);
        this.size++;
    }

    // 真正的添加方法, 递归
    private Node<V> add(Node<V> root, V data) {
        // 递归终止条件
        if (root == null) {
            return new Node<>(data);
        }
        // 递归操作
        if (data.compareTo(root.data) == 0) {
            root.frequency++;
            this.size--;// 元素重复, 此次添加元素不会改变size变量, 故在此进行减1操作
            return root;
        } else if (data.compareTo(root.data) > 0) {
            root.right = add(root.right, data);
        } else {
            root.left = add(root.left, data);
        }
        return root;
    }

二分搜索树的遍历:

前、中、后序遍历:

三种遍历方式均由递归实现, 且在编写代码时相差不多

前序遍历:先访问当前结点, 再访问当前结点的左孩子, 最后访问当前结点的右孩子

中序遍历:先访问当前结点的左孩子, 再访问当前结点, 最后访问当前结点的右孩子

后序遍历:先访问当前结点的左孩子, 再访问当前结点的右孩子, 最后访问当前结点

所谓前中后就是取决于当前结点的访问顺序, 如前序遍历中就是先对当前结点进行访问

代码演示:
    // 遍历
    // 1.前序遍历
    // 该方法只是对运行结果进行一个格式化
    public String preTraver() {
        if (size == 0) {
            return "[]";
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        preTraver(this.root, stringBuilder);
        stringBuilder.append("]");
        if (!isEmpty()) {
            stringBuilder.delete(stringBuilder.length() - 3, stringBuilder.length() - 1);
        }
        return stringBuilder.toString();
    }

    // 真正的前序遍历方法, 递归实现
    private void preTraver(Node<V> root, StringBuilder sb) {
        // 递归终止条件
        if (root == null) {
            return;
        }
        // 递归内容
        sb.append(root.data).append(", ");
        preTraver(root.left, sb);
        preTraver(root.right, sb);
    }

    // 2.中序遍历
    // 该方法只是对运行结果进行一个格式化
    public String midTraver() {
        if (size == 0) {
            return "[]";
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        midTraver(this.root, stringBuilder);
        stringBuilder.append("]");
        if (!isEmpty()) {
            stringBuilder.delete(stringBuilder.length() - 3, stringBuilder.length() - 1);
        }
        return stringBuilder.toString();
    }

    // 真正的中序遍历方法, 递归实现
    private void midTraver(Node<V> root, StringBuilder sb) {
        // 递归终止条件
        if (root == null) {
            return;
        }
        // 递归内容
        midTraver(root.left, sb);
        sb.append(root.data).append(", ");
        midTraver(root.right, sb);
    }

    // 3.后序遍历
    // 该方法只是对运行结果进行一个格式化
    public String suffixTraver() {
        if (size == 0) {
            return "[]";
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        suffixTraver(this.root, stringBuilder);
        stringBuilder.append("]");
        if (!isEmpty()) {
            stringBuilder.delete(stringBuilder.length() - 3, stringBuilder.length() - 1);
        }
        return stringBuilder.toString();
    }

    // 真正的后序遍历方法, 递归实现
    private void suffixTraver(Node<V> root, StringBuilder sb) {
        // 递归终止条件
        if (root == null) {
            return;
        }
        // 递归内容
        suffixTraver(root.left, sb);
        suffixTraver(root.right, sb);
        sb.append(root.data).append(", ");
    }
toString():

对于二分搜索树, 中序遍历的结果与自然顺序相同, 故将java中的对于二分搜索树的toString方法直接调用中序遍历的方法

代码演示:
    // 默认toString方法设置为中序遍历
    @Override
    public String toString() {
        return midTraver();
    }
层序遍历:

层序遍历时采用的是队列(先进先出)的思想:

①创建一个队列对象

②将根结点入队

③从队列中出队一个结点, 并将出队结点的左孩子和右孩子依次入队. 左孩子和右孩子的所在层数一定是出队结点的层数加1层, 由此可以确定每一个结点所在的层数

④重复③直到队列中所有结点均出队

本编文章中将层序遍历编写有些复杂, 但显示内容比较全面

如图所示, 最终的层序遍历显示内容会比实际层数多一层, 目的是填充null, 让层序遍历的结果尽可能的贴近原来二叉搜索树的模样

代码演示:
    // 层序遍历
    public String levelTravel() {
        if (isEmpty()) {
            return "[]";
        }
        Queue<AbstractMap.SimpleEntry<Node<V>, Integer>> queue = new LinkedList<>();
        queue.offer(new AbstractMap.SimpleEntry(this.root, 1));// 将根结点及其层数入队
        int preLevel = 1;
        StringBuilder sb = new StringBuilder();
        sb.append("第1层:[");
        while (!queue.isEmpty()) {
            AbstractMap.SimpleEntry<Node<V>, Integer> poll = queue.poll();
            Node<V> node = poll.getKey();// 出队结点
            Integer level = poll.getValue();// 出队结点的所在层数
            // 如果当前出队结点所在层数与上一个出队结点所在层数不相等, 则说明上一次已经遍历完成, 开启新的一层
            if (level != preLevel) {
                sb.delete(sb.length() - 2, sb.length());
                sb.append("]\n").append("第" + level + "层:[");
            }
            // 结点为空将null进行添加
            if (node == null) {
                sb.append("null, ");
            } else {
                sb.append(node.data).append(", ");
                // 无论是不是null都进行入队
                queue.offer(new AbstractMap.SimpleEntry<>(node.left, level + 1));
                queue.offer(new AbstractMap.SimpleEntry<>(node.right, level + 1));
            }
            preLevel = level;// 记录上一个出队结点的层数, 如果与当前出队结点的层数不相同进行换行
        }
        sb.delete(sb.length() - 2, sb.length());
        sb.append("]");
        return sb.toString();
    }

二分搜索树删除结点:

删除最小结点:

由二分搜索树的性质可知, 最小结点一定在左子树上,且这个结点上没有左孩子. 所以在寻找最小结点时只需要向左进行遍历即可

代码演示:
    // 删除最小元素
    public void removeMinValue() {
        // 如果树中没有元素返回null
        if (isEmpty()) {
            return;
        }
        this.root = removeMinValue(this.root);
        size--;
    }

    // 真正的删除操作, 删除最小元素
    private Node<V> removeMinValue(Node<V> root) {
        // 递归终止条件
        if (root.left == null) {
            return root.right;
        }
        // 递归操作
        root.left = removeMinValue(root.left);
        return root;
    }
删除最大结点:

由二分搜索树的性质可知, 最大结点一定在右子树上,且这个结点上没有右孩子. 所以在寻找最大结点时只需要向右进行遍历即可

代码演示:
    // 删除最大元素
    public void removeMaxValue() {
        // 如果树中没有元素返回null
        if (isEmpty()) {
            return;
        }
        this.root = removeMaxValue(this.root);
        size--;
    }

    // 删除最大元素
    private Node<V> removeMaxValue(Node<V> root) {
        // 递归终止条件
        if (root.right == null) {
            return root.left;
        }
        // 递归操作
        root.right = removeMaxValue(root.right);
        return root;
    }
删除任意结点:

在删除任意结点前, 我们需要知道几个方法:

①判断待删除元素是否存在于树中

    // 返回int类型的值表示该元素在树中出现的次数(频率), 值为0表示元素不存在
    public int getFrequency(V data) {
        Node<V> node = getFrequency(root, data);
        if (node == null) {
            return 0;
        }
        return node.frequency;
    }

    // 遍历树寻找data元素并返回频率
    private Node<V> getFrequency(Node<V> root, V data) {
        // 递归终止条件
        if (root == null) {
            return null;
        }
        // 如果找到了返回该元素出现次数
        if (data.compareTo(root.data) == 0) {
            return root;
        }
        // 递归操作
        else if (data.compareTo(root.data) > 0) {
            return getFrequency(root.right, data);
        } else {
            return getFrequency(root.left, data);
        }
    }

    // 返回布尔值判断该元素在树中是否存在
    public boolean contains(V data) {
        Node<V> node = getFrequency(root, data);
        if (node == null || node.frequency == 0) {
            return false;
        }
        return true;
    }

②获取一棵二叉搜索树上的最小值/最大值(取决于删除时如何进行操作, 本篇文章使用的是获取最小值, 不过本篇文章也提供了获取最大值的方法)

    // 获取最小值
    public V getMinValue() {
        if (isEmpty()) {
            return null;
        }
        return getMinValue(this.root).data;
    }

    // 真正获取最小值操作
    private Node<V> getMinValue(Node<V> root) {
        // 递归终止条件
        if (root.left == null) {
            return root;
        }
        // 递归操作
        return getMinValue(root.left);
    }

    // 获取最大值
    public V getMaxValue() {
        if (isEmpty()) {
            return null;
        }
        return getMaxValue(this.root).data;
    }

    // 真正获取最大值操作
    private Node<V> getMaxValue(Node<V> root) {
        // 递归终止条件
        if (root.right == null) {
            return root;
        }
        // 递归操作
        return getMaxValue(root.right);
    }

知道了上述方法后, 来看一下删除时应该如何对树进行操作

①待删除结点的左子树或右子树为空时, 将待删除结点的右子树或左子树挂接到当前结点的位置. 例: 删除这棵二叉搜索树中元素值为56的结点, 56结点的右子树为空

②待删除结点的左子树和右子树均不为空, 需要将待删除结点右子树上最小结点移动到待删除结点的位置上(或将待删除结点左子树上最大结点移动到待删除结点的位置上), 本篇文章采用的是蓝字部分的方法. 例:删除这棵二叉搜索树中元素值为47结点, 47结点的左、右子树均不为空

代码演示:
    // 删除任意元素, 返回布尔值, 表示是否删除成功
    public boolean remove(V data) {
        if (isEmpty() || !contains(data)) {
            return false;
        }
        this.root = remove(this.root, data);
        size--;
        return true;
    }

    // 真正的自定义删除操作
    private Node<V> remove(Node<V> root, V data) {
        // 递归的终止条件
        if (root == null) {
            return null;
        }
        if (data.compareTo(root.data) == 0) {
            // 递归操作
            if (root.right == null) {
                root = root.left;
            } else if (root.left == null) {
                root = root.right;
            } else {
                Node<V> node = getMinValue(root.right);
                node.right = removeMinValue(root.right);
                node.left = root.left;
                root.left = root.right = null;// 断开要删除结点与左右子树断开关联
                root = node;
            }
        } else if (data.compareTo(root.data) > 0) {
            root.right = remove(root.right, data);
        } else {
            root.left = remove(root.left, data);
        }
        return root;
    }

完整代码:

// <V extends Comparable<V>>表示存储类型必须实现Comparable接口, 同时还需要让该类型是该接口泛型的类型或其子类
public class BinarySearchTree<V extends Comparable<V>> {
    private Node<V> root;
    private int size;// 保存存储结点的个数

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

    private static class Node<V> {
        V data;
        int frequency;// 出现的次数
        Node<V> left;
        Node<V> right;

        public Node() {
        }

        public Node(V data) {
            this.data = data;
            frequency = 1;
        }
    }

    // 添加元素, 重复元素使用频率去记数, 一棵树上同一个元素只记录一次
    public void add(V data) {
        this.root = add(this.root, data);
        this.size++;
    }

    // 真正的添加方法, 递归
    private Node<V> add(Node<V> root, V data) {
        // 递归终止条件
        if (root == null) {
            return new Node<>(data);
        }
        // 递归操作
        if (data.compareTo(root.data) == 0) {
            root.frequency++;
            this.size--;
            return root;
        } else if (data.compareTo(root.data) > 0) {
            root.right = add(root.right, data);
        } else {
            root.left = add(root.left, data);
        }
        return root;
    }

    // 遍历
    // 1.前序遍历
    public String preTraver() {
        if (size == 0) {
            return "[]";
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        preTraver(this.root, stringBuilder);
        stringBuilder.append("]");
        if (!isEmpty()) {
            stringBuilder.delete(stringBuilder.length() - 3, stringBuilder.length() - 1);
        }
        return stringBuilder.toString();
    }

    // 真正的前序遍历方法, 递归实现
    private void preTraver(Node<V> root, StringBuilder sb) {
        // 递归终止条件
        if (root == null) {
            return;
        }
        // 递归内容
        sb.append(root.data).append(", ");
        preTraver(root.left, sb);
        preTraver(root.right, sb);
    }

    // 2.中序遍历
    public String midTraver() {
        if (size == 0) {
            return "[]";
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        midTraver(this.root, stringBuilder);
        stringBuilder.append("]");
        if (!isEmpty()) {
            stringBuilder.delete(stringBuilder.length() - 3, stringBuilder.length() - 1);
        }
        return stringBuilder.toString();
    }

    // 真正的中序遍历方法, 递归实现
    private void midTraver(Node<V> root, StringBuilder sb) {
        // 递归终止条件
        if (root == null) {
            return;
        }
        // 递归内容
        midTraver(root.left, sb);
        sb.append(root.data).append(", ");
        midTraver(root.right, sb);
    }

    // 3.后序遍历
    public String suffixTraver() {
        if (size == 0) {
            return "[]";
        }
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        suffixTraver(this.root, stringBuilder);
        stringBuilder.append("]");
        if (!isEmpty()) {
            stringBuilder.delete(stringBuilder.length() - 3, stringBuilder.length() - 1);
        }
        return stringBuilder.toString();
    }

    // 真正的后序遍历方法, 递归实现
    private void suffixTraver(Node<V> root, StringBuilder sb) {
        // 递归终止条件
        if (root == null) {
            return;
        }
        // 递归内容
        suffixTraver(root.left, sb);
        suffixTraver(root.right, sb);
        sb.append(root.data).append(", ");
    }

    // 默认toString方法设置为中序遍历
    @Override
    public String toString() {
        return midTraver();
    }

    // 层序遍历
    @SuppressWarnings({"all"})
    public String levelTravel() {
        if (isEmpty()) {
            return "[]";
        }
        Queue<AbstractMap.SimpleEntry<Node<V>, Integer>> queue = new LinkedList<>();
        queue.offer(new AbstractMap.SimpleEntry(this.root, 1));
        int preLevel = 1;
        StringBuilder sb = new StringBuilder();
        sb.append("第1层:[");
        while (!queue.isEmpty()) {
            AbstractMap.SimpleEntry<Node<V>, Integer> poll = queue.poll();
            Node<V> node = poll.getKey();// 出队结点
            Integer level = poll.getValue();// 出队结点的所在层数
            if (level != preLevel) {
                sb.delete(sb.length() - 2, sb.length());
                sb.append("]\n").append("第" + level + "层:[");
            }
            if (node == null) {
                sb.append("null, ");
            } else {
                sb.append(node.data).append(", ");
                // 无论是不是null都进行入队
                queue.offer(new AbstractMap.SimpleEntry<>(node.left, level + 1));
                queue.offer(new AbstractMap.SimpleEntry<>(node.right, level + 1));
            }
            preLevel = level;// 记录上一个出队结点的层数, 如果与当前出队结点的层数不相同进行换行
        }
        sb.delete(sb.length() - 2, sb.length());
        sb.append("]");
        return sb.toString();
    }

    // 返回int类型的值表示该元素在树中出现的次数(频率), 值为0表示元素不存在
    public int getFrequency(V data) {
        Node<V> node = getFrequency(root, data);
        if (node == null) {
            return 0;
        }
        return node.frequency;
    }

    // 遍历树寻找data元素并返回频率
    private Node<V> getFrequency(Node<V> root, V data) {
        // 递归终止条件
        if (root == null) {
            return null;
        }
        // 如果找到了返回该元素出现次数
        if (data.compareTo(root.data) == 0) {
            return root;
        }
        // 递归操作
        else if (data.compareTo(root.data) > 0) {
            return getFrequency(root.right, data);
        } else {
            return getFrequency(root.left, data);
        }
    }

    // 返回布尔值判断该元素在树中是否存在
    public boolean contains(V data) {
        Node<V> node = getFrequency(root, data);
        if (node == null || node.frequency == 0) {
            return false;
        }
        return true;
    }

    // 删除最小元素
    public void removeMinValue() {
        // 如果树中没有元素返回null
        if (isEmpty()) {
            return;
        }
        this.root = removeMinValue(this.root);
        size--;
    }

    // 真正的删除操作, 删除最小元素
    private Node<V> removeMinValue(Node<V> root) {
        // 递归终止条件
        if (root.left == null) {
            return root.right;
        }
        // 递归操作
        root.left = removeMinValue(root.left);
        return root;
    }

    // 删除最大元素
    public void removeMaxValue() {
        // 如果树中没有元素返回null
        if (isEmpty()) {
            return;
        }
        this.root = removeMaxValue(this.root);
        size--;
    }

    // 删除最大元素
    private Node<V> removeMaxValue(Node<V> root) {
        // 递归终止条件
        if (root.right == null) {
            return root.left;
        }
        // 递归操作
        root.right = removeMaxValue(root.right);
        return root;
    }

    // 最麻烦的删除, 自定义删除, 返回布尔值, 表示是否删除成功
    public boolean remove(V data) {
        if (isEmpty() || !contains(data)) {
            return false;
        }
        this.root = remove(this.root, data);
        size--;
        return true;
    }

    // 真正的自定义删除操作
    private Node<V> remove(Node<V> root, V data) {
        // 递归的终止条件
        if (root == null) {
            return null;
        }
        if (data.compareTo(root.data) == 0) {
            // 递归操作
            if (root.right == null) {
                root = root.left;
            } else if (root.left == null) {
                root = root.right;
            } else {
                Node<V> node = getMinValue(root.right);
                node.right = removeMinValue(root.right);
                node.left = root.left;
                root.left = root.right = null;// 断开要删除结点与左右子树断开关联
                root = node;
            }
        } else if (data.compareTo(root.data) > 0) {
            root.right = remove(root.right, data);
        } else {
            root.left = remove(root.left, data);
        }
        return root;
    }

    // 获取最小值
    public V getMinValue() {
        if (isEmpty()) {
            return null;
        }
        return getMinValue(this.root).data;
    }

    // 真正获取最小值操作
    private Node<V> getMinValue(Node<V> root) {
        // 递归终止条件
        if (root.left == null) {
            return root;
        }
        // 递归操作
        return getMinValue(root.left);
    }

    // 获取最大值
    public V getMaxValue() {
        if (isEmpty()) {
            return null;
        }
        return getMaxValue(this.root).data;
    }

    // 真正获取最大值操作
    private Node<V> getMaxValue(Node<V> root) {
        // 递归终止条件
        if (root.right == null) {
            return root;
        }
        // 递归操作
        return getMaxValue(root.right);
    }

    // 获取树中元素个数, 一个元素只记录一次
    public int getUniqueSize() {
        return this.size;
    }

    // 获取树中所有(实际)元素个数, 重复的也算
    public int getRealSize() {
        return getRealSize(this.root);
    }

    // 实际获取实际元素个数的操作
    private int getRealSize(Node<V> root) {
        // 递归终止条件
        if (root == null) {
            return 0;
        }
        // 递归操作
        return root.frequency + getRealSize(root.left) + getRealSize(root.right);
    }

    // 判断是否为空
    public boolean isEmpty() {
        return this.size == 0;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Black—slience

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

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

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

打赏作者

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

抵扣说明:

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

余额充值