61-二分搜索树BST

本文详细介绍了二分搜索树(BST)的基本概念、主要操作(插入、查找、删除最大值和最小值)以及相应的Java代码实现。讨论了二分查找在查找中的应用,并分析了在不同情况下的性能,包括最优和最差的情况。此外,还提到了JDK底层的Map结构,如二分搜索树和哈希表。
摘要由CSDN通过智能技术生成

目录

1.概念

2.操作

2.1.插入add

2.2.查找contains/maximum/minimum

2.2.1.查找BST中是否包含指定值->二分查找boolean contains(int val)

2.2.2.返回BST的最大值int maximum()

2.2.3.返回BST的最小值int minimum()

2.3.删除

2.3.1.删除最大值

2.3.2.删除最小值

2.3.3.删除任意元素

3.方法实现

3.1.向BST中添加一个新元素,用户使用

3.2.向以root为根节点的BST中添加一个新元素value

3.3.判断以root为根节点的BST中是否包含指定元素val,用户使用

3.4.判断以root为根节点的BST中是否包含指定元素val

3.5.找到当前以root为根节点的BST中的最大值节点,用户使用

3.6.找到当前以root为根节点的BST中的最大值节点

3.7.找到当前以root为根节点的BST中的最小值节点,用户使用

3.8.找到当前以root为根节点的BST中的最小值节点

3.9.删除最小值节点,返回最小值,用户使用

3.10.删除以当前root为根节点的BST中的最小值节点

3.11.删除最大值节点,返回最大值,用户使用

3.12.删除以当前root为根节点的BST中的最大值节点

3.13.在以root为根节点的二叉树中,删除值为val的节点,用户使用

3.14.在以root为根节点的二叉树中,删除值为val的节点

3.15.先序遍历二分搜索树

3.16.打印当前BST的深度,每进入下一层就多两个--

3.17.toString方法

4.总代码实现

5.测试实现

6.性能分析


JDK底层的Map有2种结构:

  1. 二分搜素树
  2. 哈希表

1.概念

二分搜索树(BST-Binary Search Tree),又称二叉搜索树、二叉排序树,它要么是一棵空树,要么是具有以下性质的二叉树:

  • 是棵二叉树(没有要求平衡性)。
  • 节点值:左孩子<根节点<右孩子,递归定义,BST中序遍历是一个升序集合,JDK的BST不存在重复元素。
  • 存储的元素必须具备可比较性(自定义的类实现Comparable或者传入比较器Comparator)。
  • 关于最值问题:
  1. 最小值:第一个node.left == null的节点。
  2. 最大值:第一个node.right == null的节点。
  • 它的左右子树也分别为二叉搜索树。

2.操作

BST中每个操作都对应2个方法:一个public,用户使用;一个private,内部调用递归实现,对外界不可见。

2.1.插入add

向BST中添加一个元素,一定是添加到叶子节点,此处我们定义的BST不存在重复元素。

2.2.查找contains/maximum/minimum

2.2.1.查找BST中是否包含指定值->二分查找boolean contains(int val)

2.2.2.返回BST的最大值int maximum()

最大值一定出现在右树,一路向右。即为一直向右子树查找过程中碰到的第一个node.right == null,node即为所求。

2.2.3.返回BST的最小值int minimum()

最小值一定出现在左树,一路向左。即为一直向左子树查找过程中碰到的第一个node.left == null,node即为所求。

注:maximum和minimum的遍历是单支树遍历,不算二分查找,遍历过程就是之前的链表遍历。

2.3.删除

2.3.1.删除最大值

最大值的右孩子一定为空,只需要将左子树连接即可。

2.3.2.删除最小值

最小值的左孩子一定为空,只需要将右子树连接即可。

2.3.3.删除任意元素

3.方法实现

3.1.向BST中添加一个新元素,用户使用

/**
 * 向BST中添加一个新元素,用户使用
 * @param value
 */
public void add(int value) {
    root = add(root,value);
}

3.2.向以root为根节点的BST中添加一个新元素value

/**
 * 向以root为根节点的BST中添加一个新元素value
 * @param root
 * @param value
 * @return 返回添加元素后的根节点
 */
private Node add(Node root, int value) {
    //当root为空时,此时走到叶子节点,创建新节点插入值
    if(root == null) {
        root = new Node(value);
        size++;
        return root;
    }
    //此时还没走到根节点,需要比较value和根节点值的大小关系
    if(value < root.val) {
        //在左树中添加
        root.left = add(root.left, value);
        return root;
    }
    //在右树中添加
    root.right = add(root.right, value);
    return root;
}

3.3.判断以root为根节点的BST中是否包含指定元素val,用户使用

/**
 * 判断以root为根节点的BST中是否包含指定元素val,用户使用
 * @param val
 * @return
 */
public boolean contains(int val) {
    return contains(root, val);
}

3.4.判断以root为根节点的BST中是否包含指定元素val

/**
 * 判断以root为根节点的BST中是否包含指定元素val
 * @param root
 * @param val
 * @return
 */
private boolean contains(Node root, int val) {
    if(root == null) {
        return false;
    }
    //根节点恰好就是待查找到的值
    else if(root.val == val) return true;
    //左树中找
    else if(val < root.val) return contains(root.left, val);
    //右树中找
    return contains(root.right, val);
}

3.5.找到当前以root为根节点的BST中的最大值节点,用户使用

/**
 * 找到当前以root为根节点的BST中的最大值节点,用户使用
 * @return
 */
public int maximum() {
    if(size == 0) {
        throw new NoSuchElementException("bst is empty!");
    }
    //找到最大值所在的节点
    Node maxNode = maximum(root);
    return maxNode.val;
}

3.6.找到当前以root为根节点的BST中的最大值节点

/**
 * 找到当前以root为根节点的BST中的最大值节点
 * @param root
 * @return
 */
private Node maximum(Node root) {
    if(root.right == null) {
        //此时右孩子为空,则root就是最大值节点
        return root;
    }
    return maximum(root.right);
}

3.7.找到当前以root为根节点的BST中的最小值节点,用户使用

/**
 * 找到当前以root为根节点的BST中的最小值节点,用户使用
 * @return
 */
public int minimum() {
    if(size == 0) {
        throw new NoSuchElementException("bst is empty!");
    }
    //找到最小值所在的节点
    Node minNode = minimum(root);
    return minNode.val;
}

3.8.找到当前以root为根节点的BST中的最小值节点

/**
 * 找到当前以root为根节点的BST中的最小值节点
 * @param root
 * @return
 */
private Node minimum(Node root) {
    if(root.left == null) {
        //此时左孩子为空,则root就是最大值节点
        return root;
    }
    return minimum(root.left);
}

3.9.删除最小值节点,返回最小值,用户使用

/**
 * 删除最小值节点,返回最小值,用户使用
 * @return
 */
public int removeMin() {
    int min = minimum();
    root = removeMin(root);
    return min;
}

3.10.删除以当前root为根节点的BST中的最小值节点

/**
 * 删除以当前root为根节点的BST中的最小值节点
 * @param root
 * @return 返回删除后的根节点
 */
private Node removeMin(Node root) {
    if(root.left == null) {
        //此时root为最小值节点
        //将右树返回
        Node right = root.right;
        //断支 等同于 链表删除时,node.next = null;
        root.right = null;
        size--;
        return right;
    }
    root.left = removeMin(root.left);
    return root;
}

3.11.删除最大值节点,返回最大值,用户使用

/**
 * 删除最大值节点,返回最大值,用户使用
 * @return
 */
public int removeMax() {
    int max = maximum();
    root = removeMax(root);
    return max;
}

3.12.删除以当前root为根节点的BST中的最大值节点

/**
 * 删除以当前root为根节点的BST中的最大值节点
 * @param root
 * @return 返回删除后的根节点
 */
private Node removeMax(Node root) {
    if(root.right == null) {
        //此时root为最大值节点
        //将左树返回
        Node left = root.left;
        //断支 等同于 链表删除时,node.next = null;
        root.left = null;
        size--;
        return left;
    }
    root.right = removeMax(root.right);
    return root;
}

3.13.在以root为根节点的二叉树中,删除值为val的节点,用户使用

/**
 * 在以root为根节点的二叉树中,删除值为val的节点,用户使用
 * @param val
 */
public void remove(int val) {
    root = remove(root, val);
}

3.14.在以root为根节点的二叉树中,删除值为val的节点

/**
 * Hibbard Deletion
 * 在以root为根节点的二叉树中,删除值为val的节点
 * @param root
 * @param val
 * @return 返回删除后的根节点
 */
private Node remove(Node root, int val) {
    if(root == null) {
        //都把BST遍历完了,也没找到值为val的节点
        return null;
    } else if(val < root.val) {
        //在左树删除
        root.left = remove(root.left, val);
        return root;
    } else if(val > root.val) {
        //在右树删除
        root.right = remove(root.right, val);
        return root;
    } else {
        //此时root就是待删除的节点
        if(root.left == null) {
            //只有右孩子,返回右孩子即可
            Node right = root.right;
            root.right = null;
            size--;
            return right;
        }
        if(root.right == null) {
            //只有左孩子,返回左孩子即可
            Node left = root.left;
            root.left = null;
            size--;
            return left;
        }
        //此时说明root.left != null && root.right != null
        //找到root的后继节点
        Node successor = minimum(root.right);
        //在右子树中删除最小值,连接为successor的右子树
        //在右子树删除后继节点的时候已经size--了
        //此时是把root替换为successor,因此size不用再--
        successor.right = removeMin(root.right);
        //连接root的左子树为successor的左子树
        successor.left = root.left;
        root.left = root.right = null;
        //此处为何没有size--?
        //在右子树删除后继节点的时候已经size--了
        //此时是把root替换为successor,因此size不用再--
        return successor;
    }
}

3.15.先序遍历二分搜索树

/**
 * 先序遍历二分搜索树
 * @param root BST的根节点
 * @param depth 当前树的深度
 * @param sb
 */
private void generateBSTString(Node root, int depth, StringBuilder sb) {
    if(root == null) {
        sb.append(generateBSTDepth(depth)).append("NULL\n");
        return;
    }
    //先根节点
    sb.append(generateBSTDepth(depth)).append(root.val + "\n");
    //递归访问左子树
    generateBSTString(root.left, depth + 1, sb);
    //递归访问右子树
    generateBSTString(root.right, depth + 1, sb);
}

3.16.打印当前BST的深度,每进入下一层就多两个--

/**
 * 打印当前BST的深度,每进入下一层就多两个--
 * @param depth
 * @return
 */
private String generateBSTDepth(int depth) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < depth; i++) {
        sb.append("--");
    }
    return sb.toString();
}

3.17.toString方法

@Override
public String toString() {
   StringBuilder sb = new StringBuilder();
   generateBSTString(root,0,sb);
   return sb.toString();
}

4.总代码实现

import java.util.NoSuchElementException;

/**
 * 基于int的二分搜索树,不包含重复元素
 */
public class BST {
    //节点个数
    private int size;
    //根节点
    private Node root;

    //节点定义
    private class Node {
        private int val;
        private Node left;
        private Node right;

        public Node(int val) {
            this.val = val;
        }
    }

    /**
     * 向BST中添加一个新元素,用户使用
     * @param value
     */
    public void add(int value) {
        root = add(root,value);
    }

    /**
     * 向以root为根节点的BST中添加一个新元素value
     * @param root
     * @param value
     * @return 返回添加元素后的根节点
     */
    private Node add(Node root, int value) {
        //当root为空时,此时走到叶子节点,创建新节点插入值
        if(root == null) {
            root = new Node(value);
            size++;
            return root;
        }
        //此时还没走到根节点,需要比较value和根节点值的大小关系
        if(value < root.val) {
            //在左树中添加
            root.left = add(root.left, value);
            return root;
        }
        //在右树中添加
        root.right = add(root.right, value);
        return root;
    }

    /**
     * 判断以root为根节点的BST中是否包含指定元素val,用户使用
     * @param val
     * @return
     */
    public boolean contains(int val) {
        return contains(root, val);
    }

    /**
     * 判断以root为根节点的BST中是否包含指定元素val
     * @param root
     * @param val
     * @return
     */
    private boolean contains(Node root, int val) {
        if(root == null) {
            return false;
        }
        //根节点恰好就是待查找到的值
        else if(root.val == val) return true;
        //左树中找
        else if(val < root.val) return contains(root.left, val);
        //右树中找
        return contains(root.right, val);
    }

    /**
     * 找到当前以root为根节点的BST中的最大值节点,用户使用
     * @return
     */
    public int maximum() {
        if(size == 0) {
            throw new NoSuchElementException("bst is empty!");
        }
        //找到最大值所在的节点
        Node maxNode = maximum(root);
        return maxNode.val;
    }

    /**
     * 找到当前以root为根节点的BST中的最大值节点
     * @param root
     * @return
     */
    private Node maximum(Node root) {
        if(root.right == null) {
            //此时右孩子为空,则root就是最大值节点
            return root;
        }
        return maximum(root.right);
    }

    /**
     * 找到当前以root为根节点的BST中的最小值节点,用户使用
     * @return
     */
    public int minimum() {
        if(size == 0) {
            throw new NoSuchElementException("bst is empty!");
        }
        //找到最小值所在的节点
        Node minNode = minimum(root);
        return minNode.val;
    }

    /**
     * 找到当前以root为根节点的BST中的最小值节点
     * @param root
     * @return
     */
    private Node minimum(Node root) {
        if(root.left == null) {
            //此时左孩子为空,则root就是最大值节点
            return root;
        }
        return minimum(root.left);
    }

    /**
     * 删除最小值节点,返回最小值,用户使用
     * @return
     */
    public int removeMin() {
        int min = minimum();
        root = removeMin(root);
        return min;
    }

    /**
     * 删除以当前root为根节点的BST中的最小值节点
     * @param root
     * @return 返回删除后的根节点
     */
    private Node removeMin(Node root) {
        if(root.left == null) {
            //此时root为最小值节点
            //将右树返回
            Node right = root.right;
            //断支 等同于 链表删除时,node.next = null;
            root.right = null;
            size--;
            return right;
        }
        root.left = removeMin(root.left);
        return root;
    }

    /**
     * 删除最大值节点,返回最大值,用户使用
     * @return
     */
    public int removeMax() {
        int max = maximum();
        root = removeMax(root);
        return max;
    }

    /**
     * 删除以当前root为根节点的BST中的最大值节点
     * @param root
     * @return 返回删除后的根节点
     */
    private Node removeMax(Node root) {
        if(root.right == null) {
            //此时root为最大值节点
            //将左树返回
            Node left = root.left;
            //断支 等同于 链表删除时,node.next = null;
            root.left = null;
            size--;
            return left;
        }
        root.right = removeMax(root.right);
        return root;
    }

    /**
     * 在以root为根节点的二叉树中,删除值为val的节点,用户使用
     * @param val
     */
    public void remove(int val) {
        root = remove(root, val);
    }

    /**
     * Hibbard Deletion
     * 在以root为根节点的二叉树中,删除值为val的节点
     * @param root
     * @param val
     * @return 返回删除后的根节点
     */
    private Node remove(Node root, int val) {
        if(root == null) {
            //都把BST遍历完了,也没找到值为val的节点
            return null;
        } else if(val < root.val) {
            //在左树删除
            root.left = remove(root.left, val);
            return root;
        } else if(val > root.val) {
            //在右树删除
            root.right = remove(root.right, val);
            return root;
        } else {
            //此时root就是待删除的节点
            if(root.left == null) {
                //只有右孩子,返回右孩子即可
                Node right = root.right;
                root.right = null;
                size--;
                return right;
            }
            if(root.right == null) {
                //只有左孩子,返回左孩子即可
                Node left = root.left;
                root.left = null;
                size--;
                return left;
            }
            //此时说明root.left != null && root.right != null
            //找到root的后继节点
            Node successor = minimum(root.right);
            //在右子树中删除最小值,连接为successor的右子树
            //在右子树删除后继节点的时候已经size--了
            //此时是把root替换为successor,因此size不用再--
            successor.right = removeMin(root.right);
            //连接root的左子树为successor的左子树
            successor.left = root.left;
            root.left = root.right = null;
            //此处为何没有size--?
            //在右子树删除后继节点的时候已经size--了
            //此时是把root替换为successor,因此size不用再--
            return successor;
        }
    }

    /**
     * 先序遍历二分搜索树
     * @param root BST的根节点
     * @param depth 当前树的深度
     * @param sb
     */
    private void generateBSTString(Node root, int depth, StringBuilder sb) {
        if(root == null) {
            sb.append(generateBSTDepth(depth)).append("NULL\n");
            return;
        }
        //先根节点
        sb.append(generateBSTDepth(depth)).append(root.val + "\n");
        //递归访问左子树
        generateBSTString(root.left, depth + 1, sb);
        //递归访问右子树
        generateBSTString(root.right, depth + 1, sb);
    }

    /**
     * 打印当前BST的深度,每进入下一层就多两个--
     * @param depth
     * @return
     */
    private String generateBSTDepth(int depth) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < depth; i++) {
            sb.append("--");
        }
        return sb.toString();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        generateBSTString(root,0,sb);
        return sb.toString();
    }
}

5.测试实现

public class BSTTest {
    public static void main(String[] args) {
        BST bst = new BST();
        int[] data = {41,58,50,60,42,53,59,63};
        for (int i = 0; i < data.length; i++) {
            bst.add(data[i]);
        }
        bst.add(35);
        System.out.println(bst);
        System.out.println(bst.contains(29));
        System.out.println(bst.contains(13));
        System.out.println(bst.contains(100));
        System.out.println(bst.maximum());
        System.out.println(bst.minimum());
        System.out.println("最小值:" + bst.removeMin());
        System.out.println(bst);
        System.out.println("最大值:" + bst.removeMax());
        bst.remove(58);
        System.out.println(bst);
    }
}

6.性能分析

  • 看到二分搜索树,天然的查找语义=>查找过程中使用二分法。
  • 插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
  • 对有n个节点的二叉搜索树,若每个元素查找的概率相同,则二叉搜索树平均查找长度是节点在二叉树的深度的函数,即节点越深,比较次数越多。
  • 对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树。

  • 最优情况下:二叉搜索树为完全二叉树,其平均比较次数为log2N。
  • 最差情况下:二叉搜索树退化为单支树(链表),其平均查找次数为N/2。插入、删除、查找操作都变为了链表的遍历过程,时间复杂度O(N),原先是O(logN)。

改进:正因为节点值若接近于有序,BST会退化为单支树或者高度严重不平衡的树,故引入二分平衡树AVL,红黑树,添加时会进行节点的旋转。

红黑树:是一棵近似平衡的二叉搜索树,即在二叉搜索树的基础上+颜色以及红黑树性质验证,TreeMap和TreeSet即Java中利用搜索树实现的Map和Set,实际上用的是红黑树。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值