java数据结构,第八篇:二叉搜素树

用途:对无序的一组数进行二分查找

前面我们介绍了二分查找法,它可以大幅度的提升查找效率,但是前提是必须基于有序数组

而至于无序的一组数,我们可以不采用数组,而是采用另种新的数据结构:二叉搜索树

定义:

顾名思义,二叉搜索树就是一种特殊的二叉树,其每个节点都要符合以下两个要求:

  1. 左子节点要小于当前节点
  2. 右子节点要大于当前节点

实现:

下面是二叉搜索树的Java实现:

类介绍:该类为泛型类,且继承了java.lang.Comparable接口,这个接口是 Java 中的一个泛型接口,用于实现对象的自然排序(natural ordering)。它包含一个抽象方法 compareTo(T obj),用于比较当前对象与传入对象的大小。这个方法返回一个整数值,表示当前对象与传入对象的关系。通常的规则是:

  • 如果当前对象小于传入对象,则返回负整数(通常是负一)。
  • 如果当前对象等于传入对象,则返回零。
  • 如果当前对象大于传入对象,则返回正整数(通常是正一)。

1. 类的基本结构

BSTTree2 类包含两个泛型类型参数 KV,分别表示二叉搜索树中键(key)和值(value)的类型。类中还包含一个嵌套类 BSTTreeNode,用于表示二叉搜索树的节点。每个节点包含一个键、一个值、一个左子节点和一个右子节点。

2. 主要方法

2.1 插入节点

put(final K key, final V value) 方法用于向二叉搜索树中插入一个新节点。如果键已经存在,则更新对应的值。

2.2 查找节点

  • get(final K key) 方法用于查找给定键对应的值。
  • getMin() 方法用于查找二叉搜索树中的最小值。
  • getMax() 方法用于查找二叉搜索树中的最大值。

2.3 删除节点

remove(final K key) 方法用于删除给定键对应的节点。如果键不存在,它将不做任何操作。删除操作包括三种情况:节点没有左子节点、节点没有右子节点、节点既有左子节点又有右子节点。对于有两个子节点的情况,它找到节点的后继节点来进行替换。

2.4 查找前驱和后继节点

  • getPre(final K key) 方法用于查找给定键的前驱值。
  • getSucc(final K key) 方法用于查找给定键的后继值。

代码实现:

public class BSTTree2,V>
{
    public static class BSTTreeNode
    {
        K key;//键
        V value;//值
        BSTTreeNode left;//左子节点
        BSTTreeNode right;//右子节点

        /**
         * 构造方法
         * @param key 键
         */
        public BSTTreeNode(final K key)
        {
            this.key = key;
        }

        /**
         * 构造方法
         * @param key 键
         * @param value 值
         */
        public BSTTreeNode(final K key, final V value)
        {
            this.key = key;
            this.value = value;
        }

        /**
         * 构造方法
         * @param key 键
         * @param value 值
         * @param left 左子节点
         * @param right 右子节点
         */
        public BSTTreeNode(final K key, final V value, final BSTTreeNode left, final BSTTreeNode right)
        {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

    /**
     * 设置根节点
     * @param root 根节点
     */
    public void setRoot(final BSTTreeNode root)
    {
        this.root = root;
    }

    private BSTTreeNode root;//根节点

    /**
     * 

查找关键字对应的值

* @param key * @return 关键字对应的值 */ public V get(final K key) { if (key == null) { throw new IllegalArgumentException("key不能为null"); } BSTTreeNode node = root; while (node != null) { int result = key.compareTo(node.key);//比较传入的key和当前节点的key,compareTo方法是Comparable接口的方法 // 如果相等返回0,如果传入的key大于当前节点的key返回正数,如果传入的key小于当前节点的key返回负数 if (result < 0)//如果传入的key小于当前节点的key { node = node.left;//向左子树查找 } else if (result > 0)//如果传入的key大于当前节点的key { node = node.right;//向右子树查找 } else//如果传入的key等于当前节点的key { return node.value;//返回当前节点的值 } } return null;//如果没找到返回null } /** *

查找最小关键字对应值

* @return 最小关键字对应值 */ public V getMin() { return getMin(root); } private V getMin(BSTTreeNode node) { if (node == null) { return null; } //找最小关键字对应值,只要一直向左子树查找就行了,最后一个左子节点的值就是最小关键字对应值 while (node.left != null) { node = node.left; } return node.value; } /** *

查找最大关键字对应值

* @return 最大关键字对应值 */ public V getMax() { return getMax(root); } private V getMax(BSTTreeNode node) { if (node == null) { return null; } //找最大关键字对应值,只要一直向右子树查找就行了,最后一个右子节点的值就是最大关键字对应值 while (node.right != null) { node = node.right; } return node.value; } /** *

储存关键字和对应值,如果键已经存在,则更新值,没有就添加新节点

* @param key 关键字 * @return 对应值 */ public void put(final K key, final V value) { BSTTreeNode node = root; BSTTreeNode parent = null;//旨在记录node的父节点,如果键不存在,则添加新节点时需要用到 while (node != null) { parent = node; int result = key.compareTo(node.key); if (result < 0) { node = node.left; } else if (result > 0 ) { node = node.right; } else { node.value = value;//如果键已经存在,则更新值 return; } } //如果键不存在,则添加新节点 BSTTreeNode newNode = new BSTTreeNode<>(key, value);//新节点 if (parent == null)//如果树为空,parent为null就意味没有进行过while循环,也就是树为空 { root = newNode; return; } int result = key.compareTo(parent.key);//比较新节点的key和parent的key if (result < 0)//如果新节点的key小于parent的key { parent.left = newNode;//新节点作为parent的左子节点 } else //不可能等于,因为前面已经判断过键不存在,直接else就行 { parent.right = newNode;//新节点作为parent的右子节点 } } /** *

查找关键字的前驱值

* @param key 关键字 * @return 关键字的前驱值 */ public V getPre(final K key) { BSTTreeNode node = root; BSTTreeNode ancestorFromLeft = null;//自左而来的祖先节点 while (node != null) { int result = key.compareTo(node.key); if (result < 0) { node = node.left; } else if (result > 0) { ancestorFromLeft = node;//更新自左而来的祖先节点 node = node.right; } else { break; } } if (node == null) { return null; } else if (node.left != null)//如果有左子节点,就找左子树的最大值 { return getMax(node.left); } else//如果没有左子节点,就返回自左而来的祖先节点的值 { return ancestorFromLeft == null ? null : ancestorFromLeft.value; } } /** *

查找关键字的后继值

* @param key 关键字 * @return 关键字的后继值 */ public V getSucc(final K key) { BSTTreeNode node = root; BSTTreeNode ancestorFromRight = null;//自右而来的祖先节点 while (node != null) { int result = key.compareTo(node.key); if (result < 0) { ancestorFromRight = node;//更新自右而来的祖先节点 node = node.left; } else if (result > 0) { node = node.right; } else { break; } } if (node == null) { return null; } else if (node.right != null)//如果有右子节点,就找右子树的最小值 { return getMin(node.right); } else//如果没有右子节点,就返回自右而来的祖先节点的值 { return ancestorFromRight == null ? null : ancestorFromRight.value; } } /** *

删除关键字对应的值

* @param key 关键字 * @return 关键字对应的值 */ public V remove(final K key) { BSTTreeNode node = root; BSTTreeNode parent = null;//旨在记录要删除的节点的父节点,后面托孤方法需要用到 while (node != null) { int result = key.compareTo(node.key); if (result < 0) { parent = node;//更新父节点 node = node.left; } else if (result > 0) { parent = node; node = node.right; } else { break; } } //删除操作 if (node.left == null)//如果要删除的节点没有左子节点,直接将右子树托孤给父节点 { shift(parent,node,node.right); } else if (node.right == null)//如果要删除的节点没有右子节点,直接将左子树托孤给父节点 { shift(parent,node,node.left); } else//如果要删除的节点既有左子节点又有右子节点,得找到右子树的最小节点,将其托孤给父节点 { BSTTreeNode s = node.right;//用于筛选符合条件的节点用于继位 BSTTreeNode sParent = node;//继位者的父节点,如果继位者还有右子节点,需要将其托孤给继位者的父节点 while (s.left != null) { sParent = s; s = s.left; } if (s != node.right)//继位者不是嫡长子 { shift(sParent,s,s.right);//处理上位者的后事 s.right = node.right; //继位者接手先帝的嫡长子的一脉 } shift(parent,node,s);//继位 s.left = node.left;//接手先帝的次子一脉 } return null; } /** * 托孤方法 * @param parent 要删除的节点的父节点 * @param removed 要删除的节点 * @param child 要删除的节点的子节点 */ private void shift(final BSTTreeNode parent, final BSTTreeNode removed, final BSTTreeNode child) { if (parent == null)//如果要删除的节点是根节点 { root = child; } else if (removed == parent.left) { parent.left = child; } else if (removed == parent.right) { parent.right = child; } } }

使用示例:

public static void main(String[] args) 
{
    BSTTree2 bst = new BSTTree2<>();
    
    bst.put(5, "五");
    bst.put(3, "三");
    bst.put(7, "七");
    bst.put(2, "二");
    bst.put(4, "四");
    
    System.out.println("键为 3 的值是:" + bst.get(3));
    System.out.println("最小键是:" + bst.getMin());
    System.out.println("最大键是:" + bst.getMax());
    
    bst.remove(3);
    System.out.println("移除键为 3 后,键为 3 的值是:" + bst.get(3));
}

在上述示例中,我们创建了一个整型到字符串的二叉搜索树,并演示了如何插入、查找、删除节点以及获取最小值和最大值等操作。

你可能感兴趣的:(数据结构,算法)