Java--二叉搜索树1(概念)

1.搜索树模型

1)纯K模型(只需要判断关键字在不在集合中即可,没有关联的 value):Set
2)K-V模型(需要根据指定 Key 找到关联的 Value):Map

2.搜索树

2.1概念

二叉搜索树又称二叉排序树(可以是空树),它具备以下性质:
在这里插入图片描述
注意:key不能重复
           如果节点存的是 key ,此时,key 不允许重复。
           如果节点存的是 key-value 键值对,此时 key 不允许重复,value允许重复。

   public static class Node {
        int key;
        // int value;
        Node left;
        Node right;

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

    // 根节点, root 为 null 的时候表示这是个空树
    private Node root = null;

2.2查找

类似于二分查找,具体步骤如下

< 1 >如果根节点不空:
      I. if(cur.key = key){ return cur;}
      II. if(cur.key > key){ cur = cur.left ; }
      III. if(cur.key < key){ cur = cur.right ; }
< 2 >否则 返回null //没找到
在这里插入图片描述

   public Node find(int key) {
        // 查找 key 是否在树中存在. 如果存在返回对应的 Node
        Node cur = root;
        while (cur != null) {
            if (key < cur.key) {
                // 就去左子树中找
                cur = cur.left;
            } else if (key > cur.key) {
                // 就去右子树中找
                cur = cur.right;
            } else {
                // 相等就是找到了
                return cur;
            }
        }
        // 循环结束了也没找到, key 就不存在.
        return null;
    }

2.3插入

< 1 >如果树是空树(root = null),则直接插入 root=new Node(key);然后 return true;
< 2 >如果树不是空树(root != null),则按照逻辑确定插入位置,插入新结点
     I. 如果插入结点(key)已经存在,return false
     II. 查找插入结点的位置,插入新节点

注意:结点插入需知道其parent,方便插入
在这里插入图片描述
注意:二叉搜索树的插入,插入元素都是插入到叶子节点的

    // 二叉搜索树中不允许存在相同 key 的元素的.
    // 如果发现新插入的 key 重复了, 那就插入失败, 返回 false
    // 插入成功返回 true
    public boolean insert(int key) {
        if (root == null) {
            // 当前如果为空树, 直接让 root 指向 key 对应的新节点即可.
            root = new Node(key);
            return true;
        }
        // 和查找类似, 需要先找到合适的位置. 再去插入元素
        Node cur = root;
        Node parent = null;  // parent 始终指向 cur 的父节点. 和链表插入类似. 链表插入元素也需要记录指定位置的前一个元素
        while (cur != null) {
            if (key < cur.key) {
                parent = cur;
                cur = cur.left;
            } else if (key > cur.key) {
                parent = cur;
                cur = cur.right;
            } else {
                // 如果当前树村的只是 key, 发现相同的 key 就认为插入失败.
                // 如果当前树存的是键值对, 发现相同的 key 就修改值即可.
                return false;
            }
        }
        // 循环结束的时候, cur 就指向 null, 当前元素就要插入到 parent 的子树位置上.
        // 具体是插到 parent 的左子树还是右子树呢? 就那 key 和 parent 再比较一次就知道了
        if (key < parent.key) {
            // 插入到 parent 的左侧
            parent.left = new Node(key);
        } else {
            parent.right = new Node(key);
        }
        return true;
    }

2.4删除

分情况讨论:设待删除结点为 cur, 待删除结点的双亲结点为 parent
在这里插入图片描述
           在这里插入图片描述
假设待删除元素值是3:
     I. 找到这个节点的左子树的最大值或右子树的最小值
     II. 由于 2 是左子树最大值,将 2 赋值到 3 这个元素位置,此时赋值后只要删除最初的 2 这个结点即可

// 删除成功返回 true, 删除失败返回 false
    // key 在树中存在, 就删除成功.
    // key 在树中不存在, 就删除失败.
    public boolean remove(int key) {
        // 先找到要删除节点的位置, 再进行具体的删除
        // 找到这个待删除元素后, 再去判定是 a - f 中的哪种情况
        Node cur = root;
        Node parent = null;
        while (cur != null) {
            if (key < cur.key) {
                parent = cur;
                cur = cur.left;
            } else if (key > cur.key) {
                parent = cur;
                cur = cur.right;
            } else {
                // 找到要删除元素, 就是 cur 指向的节点
                // 在这个方法中去判定 a - f 这些情况并进行删除
                removeNode(parent, cur);
                return true;
            }
        }
        return false;
    }

    private void removeNode(Node parent, Node cur) {
        if (cur.left == null) {
            // 1. 要删除的元素没有左子树
            if (cur == root) {
                // 1.1 如果要删除节点为 root
                root = cur.right;
            } else if (cur == parent.left) {
                // 1.2 cur 是 parent 的左子树, 对应画图板的情况 a
                // 如果 cur 也没有右子树, 相当于
                // parent.left = null
                parent.left = cur.right;
            } else {
                // 1.3 cur 是 parent 的右子树, 对应画图板的情况 b
                parent.right = cur.right;
            }
        } else if (cur.right == null) {
            // 2. 要删除的元素没有右子树
            if (cur == root) {
                // 2.1 如果要删除节点是 root
                root = cur.left;
            } else if (cur == parent.left) {
                // 2.2 要删除节点是父节点的左子树, 对应画图板的情况 c
                parent.left = cur.left;
            } else {
                // 2.3 要删除节点是父节点的右子树, 对应画图板的情况 d
                parent.right = cur.left;
            }
        } else {
            // 3. 当前要删除节点有两个子树. 对应画图板的 e 和 f
            // 1) 先找到右子树中的最小元素(替罪羊)
            Node goatParent = cur;  // 替罪羊节点的父节点
            Node scapeGoat = cur.right; // 替罪羊节点
            while (scapeGoat.left != null) {
                goatParent = scapeGoat;
                scapeGoat = scapeGoat.left;
            }
            // 循环结束时, scapeGoat 指向了右子树中的最小值
            // 2) 把刚才找到的替罪羊的值赋给待删除节点.
            cur.key = scapeGoat.key;
            // 3) 删除替罪羊节点
            //    替罪羊节点一定没有左子树(与1.2 1.3类似 )
            if (scapeGoat == goatParent.left) {
                goatParent.left = scapeGoat.right;
            } else {
                goatParent.right = scapeGoat.right;
            }
        }
    }

3.纯K模型

public class BinarySearchTree {
    public static class Node {
        int key;
        Node left;
        Node right;

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

    private Node root = null;

    /**
     * 在搜索树中查找 key,如果找到,返回 key 所在的结点,否则返回 null
     * @param key
     * @return
     */
    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; //没找到
    }

    /**
     * 插入
     * @param key
     * @return true 表示插入成功, false 表示插入失败
     */
    public boolean insert(int key) {
        if (root == null) { //如果树为空树 直接插入
            root = new Node(key);
            return true;
        }

        Node cur = root;
        Node parent = null;
        //查找插入结点的位置
        while (cur != null) {
            if (key == cur.key) { //如果key结点存在 插入失败
                return false;
            } else if (key < cur.key) {
                parent = cur;
                cur = cur.left;
            } else {
                parent = cur;
                cur = cur.right;
            }
        }

        Node node = new Node(key);
        if (key < parent.key) {
            parent.left = node;
        } else {
            parent.right = node;
        }
        return true;
    }

    /**
     * 删除成功返回 true,失败返回 false
     * @param key
     * @return
     */
    public boolean remove(int key) {
        Node cur = root;
        Node parent = null;
        while (cur != null) {
            if (key == cur.key) {
                // 找到,准备删除
                removeNode(parent, cur);
                return true;
            } else if (key < cur.key) {
                parent = cur;
                cur = cur.left;
            } else {
                parent = cur;
                cur = cur.right;
            }
        }

        return false;
    }

    private void removeNode(Node parent, Node cur) {
        if (cur.left == null) { //左孩子为空
            if (cur == root) {
                root = cur.right;
            } else if (cur == parent.left) {
                parent.left = cur.right;
            } else {
                parent.right = cur.right;
            }
        } else if (cur.right == null) { //右孩子为空
            if (cur == root) {
                root = cur.left;
            } else if (cur == parent.left) {
                parent.left = cur.left;
            } else {
                parent.right = cur.left;
            }
        } else { //左右孩子均不为空
            Node goatParent = cur; 
            Node goat = cur.right; //寻找cur的右孩子中最小的
            while (goat.left != null) {
                goatParent = goat;
                goat = goat.left;
            }

            cur.key = goat.key;
            //cur.value = goat.value;

            if (goat == goatParent.left) {
                goatParent.left = goat.right;
            } else {
                goatParent.right = goat.right;
            }
        }
    }
    
    public static void main(String[] args) {

        // 1. 创建搜索树
        // 2. 随机插入一些数据
        // 3. 打印前序 + 中序遍历
        // 4. 查找
        
        BinarySearchTree tree = new BinarySearchTree();
        int[] keys = { 3, 9, 7, 4, 1, 6, 2, 8, 5 };
        for (int key : keys) {
            System.out.println(tree.insert(key));
        }
        System.out.println("插入重复数据");
        System.out.println(tree.insert(7));

        System.out.println("前序遍历");
        preOrder(tree.root);
        System.out.println("中序遍历");
        inOrder(tree.root);

        System.out.println(tree.search(7).key);
        System.out.println(tree.search(8).key);
        System.out.println(tree.search(5).key);
    }

    private static void inOrder(Node node) {
        if (node != null) {
            inOrder(node.left);
            System.out.println(node.key);
            inOrder(node.right);
        }
    }

    private static void preOrder(Node node) {
        if (node != null) {
            System.out.println(node.key);
            preOrder(node.left);
            preOrder(node.right);
        }
        
    }

}

4.K-V模型

二叉搜索树2(K-V模型)

5.补充

查找时间复杂度最差O(n),会退化成单支树,怎么解决?
答:采取更平衡的二叉搜索树,eg:AVL树(绝对平衡)、红黑树(TreeSet、TreeMap)等。

在设计中那些方法是static,那些事非static?
答:和类(方法-行为)关联的是static; 和对象(属性-状态)关联的是非static。

如果要求时间复杂度更低?
答:哈希表可以按照O(1)的时间复杂度完成插入查找删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值