Java数据结构与算法分析(八)二叉查找树(BST)

简介: 二叉查找树又叫二叉排序树(Binary Sort Tree),或叫二叉搜索树,简称BST。BST是一种节点值之间有次序的二叉树。

在这里插入图片描述

GitHub源码分享

项目主页:https://github.com/gozhuyinglong/blog-demos
本文源码:https://github.com/gozhuyinglong/blog-demos/tree/main/java-data-structures

1. 二叉查找树(Binary Search Tree)

二叉查找树又叫二叉排序树(Binary Sort Tree),或叫二叉搜索树,简称BST。BST是一种节点值之间有次序的二叉树。其特性是:

  • 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
  • 若任意节点的右子树不空,则右子树上所有节点的值均大于或等于它的根节点的值;
  • 任意节点的左、右子树也分别为二叉查找树;

是否二叉查找树

二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低,为$O(logN)$。用大$O$符号表示的时间复杂度:

算法 平均 最差
空间 $O(N)$ $O(N)$
搜索 $O(logN)$ $O(N)$
插入 $O(logN)$ $O(N)$
删除 $O(logN)$ $O(N)$

2. BST的实现

二叉查找树要求所有的节点元素都能够排序,所以我们的Node节点类需要实现Comparable接口,树中的两个元素可以使用compareTo方法进行比较。
我们节点中元素的类型为int型,所以该接口泛型为Comparable<Integer>,下面是具体实现:

2.1 节点类

  • element 为数据元素
  • left 为左子节点
  • right 为右子节点
class Node implements Comparable<Integer> {
   
   
    private final int element; // 数据元素
    private Node left; // 左子树
    private Node right; // 右子树

    private Node(Integer element) {
   
   
        this.element = element;
    }

    @Override
    public int compareTo(Integer o) {
   
   
        return o.compareTo(element);
    }
}

2.2 二叉查找树类

  • root 为树根,所有的操作均始于此

后面会在该类中增加其他方法,如添加、查找、删除等

class BinarySearchTree {
   
   
        private Node root; // 树根
}

3. 插入节点

向二叉查找树中插入的节点总是叶子节点,插入过程如下:

  1. 若root为空,则将插入节点设为root
  2. 当前元素与插入元素通过compareTo进行比较,若插入元素值小,并且左子节点left为空,则插入至当前节点左子节点;否则继续递归
  3. 若插入元素值大,且右子节点right为空,则插入至当前节点右子节点;否则继续递归。
  4. 若插入元素等于当前节点元素,则插入失败。注:也可以将其插入到右子节点,我这里为了方便直接放弃插入。

具体实现:
在BinarySearchTree类中添加两个方法:

  • public boolean add(int element) 为公开方法
  • private boolean add(Node node, int element)为私有方法,内部递归使用
       // 添加元素
       public boolean add(int element) {
   
   
            if (root == null) {
   
   
                root = new Node(element);
                return true;
            }
            return add(root, element);
        }
        // 添加元素(递归)
        private boolean add(Node node, int element) {
   
   
            if (node.compareTo(element) < 0) {
   
   
                if (node.left == null) {
   
   
                    node.left = new Node(element);
                    return true;
                } else {
   
   
                    return add(node.left, element);
                }
            } else if (node.compareTo(element) > 0) {
   
   
                if (node.right == null) {
   
   
                    node.right = new Node(element);
                    return true;
                } else {
   
   
                    return add(node.right, element);
                }
            } else {
   
   
                return false;
            }
        }

4. 查找节点

通过二叉查找树查找元素,其过程如下:

  1. 若root为空,则查找失败
  2. 将当前元素与目标元素对比,若相等则查找成功。
  3. 若不相等,则继续递归查找:若目标值小于当前节点值,则查找左子树;否则,查找右子树。

具体实现:
在BinarySearchTree类中添加两个方法:

  • public Node find(int element) 为公开方法
  • private Node find(Node node, int element)为私有方法,内部递归使用
      // 查找元素
      public Node find(int element) {
   
   
            if (root == null) {
   
   
                return null;
            }
            return find(root, element);
        }

        // 查询元素(递归)
        private Node find(Node node, int element) {
   
   
            if (node == null) {
   
   
                return null;
            }
            int compareResult = node.compareTo(element);
            if (compareResult < 0) {
   
   
                return find(node.left, element);
            } else if (compareResult > 0) {
   
   
                return find(node.right, element);
            } else {
   
   
                return node;
            }
        }

5. 遍历节点

BST是一个有序二叉树,通过中序遍历可顺序输出树中节点。
中序遍历过程如下:

  1. 递归遍历左子节点
  2. 输出当前节点
  3. 递归遍历右子节点

具体实现:
在BinarySearchTree类中添加两个方法:

  • public void orderPrint() 为公开方法
  • private void orderPrint(Node node)为私有方法,内部递归使用
      // 遍历节点
      public void orderPrint() {
   
   
            orderPrint(root);
        }

        // 遍历节点(递归)
        private void orderPrint(Node node) {
   
   

            if (node == null) {
   
   
                return;
            }

            // 递归左子节点
            if (node.left != null) {
   
   
                orderPrint(node.left);
            }

            // 输出当前节点
            System.out.println(node.element);

            // 递归右子节点
            if (node.right != null) {
   
   
                orderPrint(node.right);
            }

        }

6. 删除节点

删除节点最为复查,共有三种情况:

6.1 目标元素为叶子节点

叶子节点最容易删除,过程如下:

  1. 找到目标节点的父节点
  2. 判断目标节点是父节点的左子树还是右子树
  3. 若是左子树,将父节点的left设为空;否则将父节点的right设为空

6.2 目标元素即有左子树,也有右子树

该情况删除操作最为复杂,过程如下:

  1. 找到目标节点的父节点
  2. 判断目标节点是父节点的左子树还是右子树
  3. 找到右子树中最小元素(叶子节点),将其赋给临时变量minNode,再将该元素从树中删除
  4. 将目标元素的属性赋予minNode。
  5. 若目标元素是父节点的左子树,将父节点的left设为minNode;否则将父节点的right设为minNode

6.3 目标元素只有左子树,或只有右子树

删除过程如下

  1. 找到目标节点的父节点
  2. 判断目标节点是父节点的左子树还是右子树
  3. 若是左子树,将父节点的left设为目标节点不为空的子树;否则将父节点的right设为目标节点不为空的子树

具体实现
在BinarySearchTree类中添加两个方法:

  • public boolean remove(int element) 为公开方法
  • private boolean remove(Node parentNode, Node node, int element)为私有方法,内部递归使用
      // 删除节点
      public boolean remove(int element) {
   
   
            if (root == null) {
   
   
                return false;
            }
            // 如果删除的元素是root
            if (root.compareTo(element) == 0) {
   
   
                if (root.right == null) {
   
   
                    root = root.left;
                } else {
   
   
                    root.right.left = root.left;
                    root = root.right;
                }
                return true;
            }
            return remove(null, root, element);
        }

        // 删除节点(递归)
        private boolean remove(Node parentNode, Node node, int element) {
   
   
            if (node == null) {
   
   
                return false;
            }
            // 先找到目标元素
            int compareResult = node.compareTo(element);
            if (compareResult < 0) {
   
   
                return remove(node, node.left, element);
            }
            if (compareResult > 0) {
   
   
                return remove(node, node.right, element);
            }

            // 找到目标元素,判断该节点是父节点的左子树还是右子树
            boolean isLeftOfParent = false;
            if (parentNode.left != null && parentNode.left.compareTo(element) == 0) {
   
   
                isLeftOfParent = true;
            }

            // 删除目标元素
            if (node.left == null && node.right == null) {
   
    // (1)目标元素为叶子节点,直接删除
                if (isLeftOfParent) {
   
   
                    parentNode.left = null;
                } else {
   
   
                    parentNode.right = null;
                }
            } else if (node.left != null && node.right != null) {
   
    // (2)目标元素即有左子树,也有右子树
                // 找到右子树最小值(叶子节点),并将其删除
                Node minNode = findMin(node.right);
                remove(minNode.element);
                // 将该最小值替换要删除的目标节点
                minNode.left = node.left;
                minNode.right = node.right;
                if(isLeftOfParent) {
   
   
                    parentNode.left = minNode;
                } else {
   
   
                    parentNode.right = minNode;
                }

            } else {
   
    // (3)目标元素只有左子树,或只有右子树
                if (isLeftOfParent) {
   
   
                    parentNode.left = node.left != null ? node.left : node.right;
                } else {
   
   
                    parentNode.right = node.left != null ? node.left : node.right;
                }
            }
            return true;
        }
    }

7. 完整代码

该代码根据下图二叉查找树实现,其操作包括:添加、查找、遍历、删除、查询最小值、查询最大值。

二叉查找树

public class BinarySearchTreeDemo {
   
   

    public static void main(String[] args) {
   
   
        BinarySearchTree tree = new BinarySearchTree();

        System.out.println("----------------------添加元素");
        Integer[] array = {
   
   5, 2, 7, 1, 4, 3, 7, 6, 9, 8};
        for (Integer element : array) {
   
   
            System.out.printf("添加元素[%s] --> %s\n", element, tree.add(element));
        }

        System.out.println("----------------------顺序输出(中序遍历)");
        tree.orderPrint();

        System.out.println("----------------------查找元素");
        System.out.println(tree.find(7));

        System.out.println("----------------------查找最小元素");
        System.out.println(tree.findMin());

        System.out.println("----------------------查找最大元素");
        System.out.println(tree.findMax());

        System.out.println("----------------------是否包含元素");
        System.out.println("是否包含[0] --> \t" + tree.contains(0));
        System.out.println("是否包含[2] --> \t" + tree.contains(2));

        System.out.println("----------------------删除目标元素");
        System.out.println("删除[0] --> \t" + tree.remove(0));
        tree.orderPrint();
        System.out.println("删除[1] --> \t" + tree.remove(1));
        tree.orderPrint();
        System.out.println("删除[4] --> \t" + tree.remove(4));
        tree.orderPrint();
        System.out.println("删除[7] --> \t" + tree.remove(7));
        tree.orderPrint();

    }

    private static class BinarySearchTree {
   
   
        private Node root; // 树根

        /**
         * 添加元素
         *
         * @param element
         * @return
         */
        public boolean add(int element) {
   
   
            if (root == null) {
   
   
                root = new Node(element);
                return true;
            }
            return add(root, element);
        }

        /**
         * 添加元素(递归)
         *
         * @param node
         * @param element
         * @return
         */
        private boolean add(Node node, int element) {
   
   
            if (node.compareTo(element) < 0) {
   
   
                if (node.left == null) {
   
   
                    node.left = new Node(element);
                    return true;
                } else {
   
   
                    return add(node.left, element);
                }
            } else if (node.compareTo(element) > 0) {
   
   
                if (node.right == null) {
   
   
                    node.right = new Node(element);
                    return true;
                } else {
   
   
                    return add(node.right, element);
                }
            } else {
   
   
                return false;
            }
        }

        /**
         * 查询元素
         *
         * @param element
         * @return
         */
        public Node find(int element) {
   
   
            if (root == null) {
   
   
                return null;
            }
            return find(root, element);
        }

        /**
         * 查询元素(递归)
         *
         * @param node
         * @param element
         * @return
         */
        private Node find(Node node, int element) {
   
   
            if (node == null) {
   
   
                return null;
            }
            int compareResult = node.compareTo(element);
            if (compareResult < 0) {
   
   
                return find(node.left, element);
            } else if (compareResult > 0) {
   
   
                return find(node.right, element);
            } else {
   
   
                return node;
            }
        }

        /**
         * 查找最大值
         *
         * @return
         */
        public Node findMax() {
   
   
            return findMax(root);
        }

        /**
         * 查找最大值(递归)
         *
         * @param node
         * @return
         */
        private Node findMax(Node node) {
   
   
            if (node.right == null) {
   
   
                return node;
            }
            return findMax(node.right);
        }

        /**
         * 查找最小值
         *
         * @return
         */
        private Node findMin() {
   
   
            return findMin(root);
        }

        /**
         * 查找最小值(递归)
         *
         * @param node
         * @return
         */
        private Node findMin(Node node) {
   
   
            if (node.left == null) {
   
   
                return node;
            }
            return findMin(node.left);
        }

        /**
         * 顺序输出
         */
        public void orderPrint() {
   
   
            orderPrint(root);
        }


        /**
         * 顺序输出(递归)
         *
         * @param node
         */
        private void orderPrint(Node node) {
   
   

            if (node == null) {
   
   
                return;
            }

            // 递归左子节点
            if (node.left != null) {
   
   
                orderPrint(node.left);
            }

            // 输出当前节点
            System.out.println(node.element);

            // 递归右子节点
            if (node.right != null) {
   
   
                orderPrint(node.right);
            }

        }

        /**
         * 是否包含某值
         *
         * @param element
         * @return
         */
        public boolean contains(int element) {
   
   
            if (find(element) == null) {
   
   
                return false;
            }
            return true;
        }

        /**
         * 删除目标元素
         *
         * @param element
         * @return
         */
        public boolean remove(int element) {
   
   
            if (root == null) {
   
   
                return false;
            }
            // 如果删除的元素是root
            if (root.compareTo(element) == 0) {
   
   
                if (root.right == null) {
   
   
                    root = root.left;
                } else {
   
   
                    root.right.left = root.left;
                    root = root.right;
                }
                return true;
            }
            return remove(null, root, element);
        }

        /**
         * 删除目标元素(递归),有三种情况:
         * (1)目标元素为叶子节点
         * (2)目标元素即有左子树,也有右子树
         * (3)目标元素只有左子树,或只有右子树
         *
         * @param parentNode 当前节点的父节点
         * @param node       当前节点(若当前节点上的元素与要删除的元素匹配,则删除当前节点)
         * @param element    要删除的元素
         * @return
         */
        private boolean remove(Node parentNode, Node node, int element) {
   
   
            if (node == null) {
   
   
                return false;
            }
            // 先找到目标元素
            int compareResult = node.compareTo(element);
            if (compareResult < 0) {
   
   
                return remove(node, node.left, element);
            }
            if (compareResult > 0) {
   
   
                return remove(node, node.right, element);
            }

            // 找到目标元素,判断该节点是父节点的左子树还是右子树
            boolean isLeftOfParent = false;
            if (parentNode.left != null && parentNode.left.compareTo(element) == 0) {
   
   
                isLeftOfParent = true;
            }

            // 删除目标元素
            if (node.left == null && node.right == null) {
   
    // (1)目标元素为叶子节点,直接删除
                if (isLeftOfParent) {
   
   
                    parentNode.left = null;
                } else {
   
   
                    parentNode.right = null;
                }
            } else if (node.left != null && node.right != null) {
   
    // (2)目标元素即有左子树,也有右子树
                // 找到右子树最小值(叶子节点),并将其删除
                Node minNode = findMin(node.right);
                remove(minNode.element);
                // 将该最小值替换要删除的目标节点
                minNode.left = node.left;
                minNode.right = node.right;
                if(isLeftOfParent) {
   
   
                    parentNode.left = minNode;
                } else {
   
   
                    parentNode.right = minNode;
                }

            } else {
   
    // (3)目标元素只有左子树,或只有右子树
                if (isLeftOfParent) {
   
   
                    parentNode.left = node.left != null ? node.left : node.right;
                } else {
   
   
                    parentNode.right = node.left != null ? node.left : node.right;
                }
            }
            return true;
        }
    }

    private static class Node implements Comparable<Integer> {
   
   
        private final Integer element; // 数据元素
        private Node left; // 左子树
        private Node right; // 右子树

        private Node(Integer element) {
   
   
            this.element = element;
        }

        @Override
        public int compareTo(Integer o) {
   
   
            return o.compareTo(element);
        }

        @Override
        public String toString() {
   
   
            return "Node{" +
                    "element=" + element +
                    '}';
        }
    }
}

输出结果:

----------------------添加元素
添加元素[5] --> true
添加元素[2] --> true
添加元素[7] --> true
添加元素[1] --> true
添加元素[4] --> true
添加元素[3] --> true
添加元素[7] --> false
添加元素[6] --> true
添加元素[9] --> true
添加元素[8] --> true
----------------------顺序输出(中序遍历)
1
2
3
4
5
6
7
8
9
----------------------查找元素
Node{element=7}
----------------------查找最小元素
Node{element=1}
----------------------查找最大元素
Node{element=9}
----------------------是否包含元素
是否包含[0] -->     false
是否包含[2] -->     true
----------------------删除目标元素
删除[0] -->     false
1
2
3
4
5
6
7
8
9
删除[1] -->     true
2
3
4
5
6
7
8
9
删除[4] -->     true
2
3
5
6
7
8
9
删除[7] -->     true
2
3
5
6
8
9
相关文章
|
12月前
|
设计模式 算法 搜索推荐
Java 设计模式之策略模式:灵活切换算法的艺术
策略模式通过封装不同算法并实现灵活切换,将算法与使用解耦。以支付为例,微信、支付宝等支付方式作为独立策略,购物车根据选择调用对应支付逻辑,提升代码可维护性与扩展性,避免冗长条件判断,符合开闭原则。
2881 35
|
人工智能 算法 NoSQL
LRU算法的Java实现
LRU(Least Recently Used)算法用于淘汰最近最少使用的数据,常应用于内存管理策略中。在Redis中,通过`maxmemory-policy`配置实现不同淘汰策略,如`allkeys-lru`和`volatile-lru`等,采用采样方式近似LRU以优化性能。Java中可通过`LinkedHashMap`轻松实现LRUCache,利用其`accessOrder`特性和`removeEldestEntry`方法完成缓存淘汰逻辑,代码简洁高效。
657 0
|
12月前
|
存储 算法 搜索推荐
《数据之美》:Java数据结构与算法精要
本系列深入探讨数据结构与算法的核心原理及Java实现,涵盖线性与非线性结构、常用算法分类、复杂度分析及集合框架应用,助你提升程序效率,掌握编程底层逻辑。
|
12月前
|
存储 机器学习/深度学习 监控
网络管理监控软件的 C# 区间树性能阈值查询算法
针对网络管理监控软件的高效区间查询需求,本文提出基于区间树的优化方案。传统线性遍历效率低,10万条数据查询超800ms,难以满足实时性要求。区间树以平衡二叉搜索树结构,结合节点最大值剪枝策略,将查询复杂度从O(N)降至O(logN+K),显著提升性能。通过C#实现,支持按指标类型分组建树、增量插入与多维度联合查询,在10万记录下查询耗时仅约2.8ms,内存占用降低35%。测试表明,该方案有效解决高负载场景下的响应延迟问题,助力管理员快速定位异常设备,提升运维效率与系统稳定性。
470 4
|
存储 算法 安全
Java中的对称加密算法的原理与实现
本文详细解析了Java中三种常用对称加密算法(AES、DES、3DES)的实现原理及应用。对称加密使用相同密钥进行加解密,适合数据安全传输与存储。AES作为现代标准,支持128/192/256位密钥,安全性高;DES采用56位密钥,现已不够安全;3DES通过三重加密增强安全性,但性能较低。文章提供了各算法的具体Java代码示例,便于快速上手实现加密解密操作,帮助用户根据需求选择合适的加密方案保护数据安全。
979 58
|
12月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
存储 机器学习/深度学习 算法
KMP、Trie树 、AC自动机‌ ,三大算法实现 优雅 过滤 netty 敏感词
KMP、Trie树 、AC自动机‌ ,三大算法实现 优雅 过滤 netty 敏感词
KMP、Trie树 、AC自动机‌ ,三大算法实现 优雅 过滤 netty  敏感词
|
机器学习/深度学习 算法 Java
Java实现林火蔓延路径算法
记录正在进行的森林防火项目中林火蔓延功能,本篇文章可以较好的实现森林防火蔓延,但还存在很多不足,如:很多参数只能使用默认值,所以蔓延范围仅供参考。(如果底层设备获取的数据充足,那当我没说)。注:因林火蔓延涉及因素太多,如静可燃物载量、矿质阻尼系数等存在估值,所以得出的结果仅供参考。
665 5
|
存储 负载均衡 算法
我们来说一说 Java 的一致性 Hash 算法
我是小假 期待与你的下一次相遇 ~
781 1
|
监控 算法 安全
基于 C# 基数树算法的网络屏幕监控敏感词检测技术研究
随着数字化办公和网络交互迅猛发展,网络屏幕监控成为信息安全的关键。基数树(Trie Tree)凭借高效的字符串处理能力,在敏感词检测中表现出色。结合C#语言,可构建高时效、高准确率的敏感词识别模块,提升网络安全防护能力。
387 2

热门文章

最新文章

  • 1
    软考中级软件设计师专项-数据结构与算法上篇
    1063
  • 2
    2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
    502
  • 3
    2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    573
  • 4
    2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    419
  • 5
    2024重生之回溯数据结构与算法系列学习(8)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    366
  • 6
    2024重生之回溯数据结构与算法系列学习之王道第2.3章节之线性表精题汇总二(5)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    390
  • 7
    2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    415
  • 8
    2024重生之回溯数据结构与算法系列学习之顺序表习题精讲【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    394
  • 9
    2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    435
  • 10
    2024重生之回溯数据结构与算法系列学习【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
    318