关于二叉搜索树,你需要知道这些

二叉搜索树是一种特殊树形结构,其中每个节点的值大于其左子树所有节点,小于其右子树所有节点。这确保了中序遍历结果为有序序列。堆与之不同,它要求父节点的值大于或小于子节点。二叉搜索树常用于增删查操作,但不支持直接修改key。插入和删除操作依赖查找效率,性能分析关键在于树的平衡。平衡二叉树如红黑树,通过特定规则保持平衡,避免性能退化。Java中的TreeMap和TreeSet即使用红黑树实现。

二叉搜索树:针对任何一颗子树来说都要满足,父节点>左子树,父节点<右子树。(父节点是中间大小的元素。
在这里插入图片描述
堆:父节点和子节点中间的关系,堆顶元素一定是最大值或者最小值

二叉搜索树中序遍历的结果是一个有序的序列


具体实现
主要有三个核心操作(增删查),对于二叉搜索树,修改操作不能针对key进行修改

package package1123;

public class BinarySearchTree {
    public static class Node{
        int key;
        int value;  //TreeSet,TreeMap
        Node left;
        Node right;

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "key=" + key +
                    ", value=" + value +
                    '}';
        }
    }
    //创建一根树根节点,初始情况下是空树,根节点指向null
    private Node root = null;

    //查找
    public Node search(int key){
        //从根节点出发,根据这个关系,决定在左子树中找还是右子树中找
        Node cur = root;
        while (cur != null){
            if (key == cur.key){
                return cur;
            }else if (key < cur.key){
                //去左子树找
                cur = cur.left;
            }else {
                //去右子树找
                cur = cur.right;
            }
        }
        return null;
    }

    //插入
    //1.找到合适位置,新的节点放到合适位置
    //2.啥时候会插入失败,约定如果当前的key在树中已经存在了,就认为插入失败,TreeSet
    //还可以这样约定,如果当前的key存在,直接修改value TreeMap //武松的故事
    public boolean insert(int key,int value){
        //1.当前要插入的树是空树的话
        if (root == null){
            root = new Node(key,value);
        }
        //2.对于一个不是空的树,就需要先找到合适的位置
        //查找的过程中,随时记录当前节点的父亲
        Node cur = root;
        Node parent = null;
        while (cur != null){
            if (key == cur.key){
                //当前的key已经存在
                return false; //TreeSet
            }else if (key < cur.key){
                parent = cur;//parent 一直指向cur的父节点
                cur = cur.left;
            }else {
                //parent 是cur的父节点,所以得更新parent

                parent = cur;
                cur = cur.right;
            }
        }
        //cur 一定是空,这点很重要
        Node newNode = new Node(key,value);
        if (key < parent.key){
            parent.left = newNode;
        }else {
            parent.right = newNode;
        }
        return true;
    }

    public boolean remove(int key){
        //记录当前位置的父节点
        Node cur = root;
        Node parent = null;
        //查找要删除元素的位置
        while (cur != null){
            if (cur.key == key){
                //找到了
                removeNode(parent,cur);
                return true;
            }else if(key < cur.key){
                parent = cur;
                cur = cur.left;
            }else {
                parent = cur;
                cur = cur.right;
            }
        }
        return false;
    }
    public void removeNode(Node parent,Node cur){
        //很多考虑的细节从这里删掉
        if (cur.left == null){
            //1.1cur就是根节点
            if (cur == root){
                root = cur.right;
            }else if(parent.left == cur){
                //cur是父节点的左子树
                parent.left = cur.right;
            }else {
                parent.right = cur.right;
            }
        }else if(cur.right == null){
            //2.1cur是根节点
            if(cur == root){
                root = cur.left;
            }else if (parent.left == cur){
                //2.1 cur 是父节点的左子树
                parent.left = cur.left;
            }else {
                //2.3 cur 是父节点的右子树
                parent.right = cur.left;
            }
        }else {
            //3.同时有左右子树的情况
            //找到替罪羊(cur右子树的最左侧元素)
            //把替罪羊的key value 赋值到待删除结点之中
            //在删除替罪羊节点
            Node scapeGoat = cur.right;
            Node scapeGoatParent = cur;
            while (scapeGoat.left != null){
                scapeGoatParent = scapeGoat;
                scapeGoat = scapeGoat.left;
            }
            cur.key = scapeGoat.key;
            cur.value = scapeGoat.value;
            //删除替罪羊节点
            if (scapeGoat == scapeGoatParent.left){
                scapeGoatParent.left = scapeGoat.right;
            }else {
                //就是cur.right 就已经是最左边的情况了。
                scapeGoatParent.right = scapeGoat.right;
            }
        }
    }
    public static void inOrder(Node root){
        //验证
        if (root == null){
            return;
        }
        inOrder(root.left);
        System.out.print(root.key + " ");
        inOrder(root.right);
    }
    public static void main(String[] args) {
        //建立一个二叉搜索树
        BinarySearchTree tree = new BinarySearchTree();
        int[] arr = {9,5,2,7,3,6,8};
        for (int x : arr){
            tree.insert(x,0);
        }
        
        tree.remove(6);
        inOrder(tree.root);
    }
}


性能分析
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

1.查找复杂度:
		最优情况下,二叉搜索树为完全二叉树,平均比较次数为log2(N)
		最坏情况下,二叉搜索树为极端的单枝树,平均比较次数 N/2

平衡二叉树的具体实现形式
2.红黑树:特点有六条规则,红黑树是一种不太严格的平衡二叉树,不能让左右子树差太多
TreeMap 和 TreeSet 即 java 中利用搜索树实现的 Map 和 Set;实际上用的是红黑树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值