二叉搜索树及Java实现

简介: 二叉搜索树及Java实现

1、概要

这里主要讲解二叉搜索树的设计及实现。
代码地址: github地址
https://github.com/fofcn/operation-system/blob/main/%E5%AE%9E%E8%B7%B5/os/src/main/java/lang/tree/BinarySearchTree.java

2、概念

二叉搜索树是用一颗二叉树来组织的,这棵树可以使用一个链表来表示,每个节点都是一个对象。

2.1、节点

每个节点都有一个父节点、左孩子和右孩子。
1.png

2.2、性质

假设x是二叉搜索树的一个节点。
如果y是x的左子树的一个节点,那么y.key<=x.key。
如果y是x的右子树的一个节点,那么y.key>=x.key。
2.png

3、操作

搜索树支持很多动态集合操作,包括SEARCH、MINIMUM、MAXIMUM、FLOOL、CELL、INSERT和DELETE等。我们主要讲解查找、插入和删除三类主要的操作。

3.1、插入

二叉搜索树的插入相对比较简单,需要在二叉搜索树上查找插入的位置,然后新建接节点插入,然后修改该节点的父级节点属性。
例如:将 10 16 4 1 12 2 0 11 18 6 分别插入到二叉搜索树中,会得到如下图所示的二叉搜索树
3.png

Java实现代码如下:

private void putInternal(Key key, Value value) {
        // 初始化二叉搜索树
        if (root == null) {
            root = new Node(key, value);
            size.incrementAndGet();
        } else {
            // 从根节点开始查找插入位置
            Node tmp = root;
            while (tmp != null) {
                int cmp = key.compareTo(tmp.key);
                // 如果待插入节点比当前节点小,那么查看该节点的左孩子是否为空,如果为空,则将待插入节点插入到左孩子
                if (cmp < 0) {
                    if (tmp.left == null) {
                        tmp.left = new Node(key, value);
                        tmp.left.parent = tmp;
                        size.incrementAndGet();
                        break;
                    }
                    tmp = tmp.left;
                    // 如果待插入节点比当前节点小,那么查看该节点的右孩子是否为空,如果为空,则将待插入节点插入到右孩子
                } else if (cmp > 0) {
                    if (tmp.right == null) {
                        tmp.right = new Node(key, value);
                        tmp.right.parent = tmp;
                        size.incrementAndGet();
                        break;
                    }
                    tmp = tmp.right;
                } else {
                    tmp.value = value;
                    break;
                }
            }
        }
    }

3.2、查找

在一颗二叉搜索树中查找一个指定关键字。
在二叉搜索树中查找一个指定关键字从树根开始查找,并沿着这棵树的一条路径向下进行查找。
例如:如果在上图中的二叉搜索树中查找节点为6的节点,那么搜索路径为10->4-6
4.png

Java实现代码如下:

/**
     * 根据key查找Key对应的节点
     * @param key 关键字
     * @return key对应的关键字,null没有找到关键字对应的节点
     */
    private Node getInternal(Key key) {
        Node tmp = root;
        int cmp;
        // 遍历树查找节点
        while (tmp != null && (cmp = key.compareTo(tmp.key)) != 0) {
            if (cmp < 0 ) {
                tmp = tmp.left;
            } else {
                tmp = tmp.right;
            }
        }

        return tmp;
    }

3.3、删除

二叉搜索树的删除稍微有点复杂,根据二叉搜索树的特性,删除基本分为三种情况:
假设待删除节点为x:

  1. 如果x没有孩子节点,那么修改该孩子的父节点为null

如下图所示:将6节点删除时直接删除并更新节点4的右孩子为null
5.png

  1. 如果x只有一个孩子节点,那么将这个孩子提升到x位置,并修改x的父节点对应的左右孩子为这个孩子节点

如下图:将12节点的值删除时,需要将16节点的左孩子更新为11,11节点的父节点更新为16
6.png

删除后如下图所示
7.png

  1. 如果x有两个孩子节点,那么就需要在x的右子树中查找x的后继节点y,并将y提升到节点x的位置,并将x的左子树成为y的左子树,将替换y的右子树为x的右子树

如下图所示:如果删除4节点,则需要从4节点的右孩子中查找右孩子中最小的节点为5,将5提升到节点4的位置,更新5的父节点为10,更新5的左孩子为1,更新6的左孩子为null
8.png

删除后如下图所示:
9.png

Java代码实现如下:

public void delete(Key key) {
        if (key == null) {
            throw new IllegalArgumentException();
        }

        Node tmp = root;
        while (tmp != null) {
            int cmp = key.compareTo(tmp.key);
            if (cmp < 0) {
                tmp = tmp.left;
            } else if (cmp > 0) {
                tmp = tmp.right;
            } else {
                // 找到了待删除的节点
                // 情况1:如果该节点既没有左孩子也没有右孩子,那么就直接删除
                // 情况2:如果该节点只有一个孩子那么直接将该孩子提升到该孩子的位置上
                // 情况3:如果该节点既有左孩子又有右孩子,则查找待删除节点的右孩子中最小的孩子,并将最小的孩子提升到待删除节点的位置

                // 左孩子为空,那么用右孩子替换为tmp
                if (tmp.left == null) {
                    transplant(tmp, tmp.right);
                } else if (tmp.right == null) {
                    // 右孩子为空,那么用左孩子替换掉tmp
                    transplant(tmp, tmp.left);
                } else {
                    // 左右孩子都不为空
                    // 查找右孩子中最小的节点
                    Node min = min(tmp.right);
                    // 右孩子中最小节点的父级节点不是tmp表示最小节点不是tmp的直接孩子
                    // 那么这种情况下,需要将最小节点的右孩子替换最小节点的位置
                    // 将最小节点的右孩子设置为tmp右孩子
                    // 将最小节点的右孩子的父设置为最小节点
                    if (min.parent != tmp) {
                        transplant(min, min.right);
                        min.right = tmp.right;
                        min.right.parent = min;
                    }

                    // 用tmp右子树最小节点替换掉tmp
                    // 最小节点的左孩子更新为tmp的左孩子
                    // 最小节点的左孩子的父节点更新为最小节点
                    transplant(tmp, min);
                    min.left = tmp.left;
                    min.left.parent = min;
                }
                tmp = null;
                break;
            }
        }
    }
private void transplant(Node del, Node child) {
        // 更新待删除节点的父节点
        if (del.parent == null) {
            root = child;
        } else if (del == del.parent.left) {
            del.parent.left = child;
        } else {
            del.parent.right = child;
        }

        // 更新孩子节点的父节点为待删除节点的父节点
        if (child != null) {
            child.parent = del.parent;
        }
    }

4 算法运行时间

假设一颗二叉搜索树的高度为h

操作 时间
插入 O(h)
查找 O(h)
删除 O(h)

最好情况分析

最好情况就是二叉搜索树完全平衡,即二叉搜索树中的每个节点都有左右两个孩子,此时树的高度O(logn),如下图所示:
10.png

最坏情况分析

最坏情况是所有节点都是按照正序插入,那么此时树的高度是n,插入、查找和删除的时间是O(n),如下图所示
11.png

平均情况分析

12.png

5、总结

  1. 二叉搜索树保证在O(n)的时间内完成插入、查找和删除
  2. 二叉搜索树平均插入和查找的时间为1.39logn
  3. 二叉搜索树平均情况运行时间依然是很快

6、参考

  1. 算法导论 二叉搜索树
目录
相关文章
|
4月前
|
存储 Java
1038. 从二叉搜索树到更大和树 --力扣 --JAVA
给定一个二叉搜索树 root (BST),请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。 提醒一下, 二叉搜索树 满足下列约束条件: 节点的左子树仅包含键 小于 节点键的节点。 节点的右子树仅包含键 大于 节点键的节点。 左右子树也必须是二叉搜索树。
38 1
|
28天前
|
Java
java如何实现一个高效的二叉搜索树(BST)?请给出时间复杂度分析。
这段内容介绍了Java中使用AVL树实现高效二叉搜索树的方法。AVL树是一种自平衡树,节点子树高度差不超过1,确保操作时间复杂度为O(log n)。代码包括了`Node`类和`AVLTree`类,实现了节点、插入、删除、查找和平衡旋转等方法。通过旋转操作,维持树的平衡,保证了搜索效率。
16 6
|
3月前
|
Java
LeetCode题解-二叉搜索树中第K小的元素-Java
LeetCode题解-二叉搜索树中第K小的元素-Java
13 0
|
4月前
|
算法 C++ Java
Java每日一练(20230504) 位1的个数、移除元素、验证二叉搜索树
Java每日一练(20230504) 位1的个数、移除元素、验证二叉搜索树
24 0
Java每日一练(20230504) 位1的个数、移除元素、验证二叉搜索树
|
4月前
|
存储 Java Serverless
二叉搜索树 和 哈希表 (JAVA)
【1月更文挑战第1天】阿里云初体验 二叉搜索树 二叉搜索树的插入 二叉搜索树的查找 二叉搜索树的删除 哈希表 哈希冲突 闭散列 线性探测法 二次探测法 开散列 二叉搜索树又称二叉排序树,它具有以下性质的二叉树或空树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 它的每颗子树也分别为二叉搜索树 而哈希表是一种插入/删除/查找时间复杂度都是O(1)的数据结构,它的查询之所以也可以如此之快的的原因就是它在查找时不需要经过任何比较,直接一次从表中得到要搜索的元素。
38 0
二叉搜索树 和 哈希表 (JAVA)
|
5月前
|
存储 算法 Java
230. 二叉搜索树中第K小的元素 --力扣 --JAVA
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
122 3
|
5月前
|
Java
98. 验证二叉搜索树 --力扣 --JAVA
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下: 节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。 -2^31 <= Node.val <= 2^31 - 1
29 0
98. 验证二叉搜索树 --力扣 --JAVA
|
5月前
|
Java
108. 将有序数组转换为二叉搜索树 --力扣 --JAVA
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。 高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
32 0
|
10月前
|
Java
二叉搜索树(二叉排序树)—Java(下)
二叉搜索树(二叉排序树)—Java(下)
|
10月前
|
Java
二叉搜索树(二叉排序树)—Java(上)
二叉搜索树(二叉排序树)—Java(上)