手写二叉排序树(二叉查找树、二叉搜索树)

手写二叉排序树(二叉查找树、二叉搜索树)

在二叉排序树中,每个根节点只能有左右两个子节点,左子节点比根节点小,右子节点比根节点大(TreeSet中不允许出现相同元素。一些场景下,如果子节点和根节点相同会将子节点放到根节点的右边)。

手写二叉排序树(二叉查找树、二叉搜索树)

//二叉搜索树
public class BinarySearchTree<T extends Comparable> {

    private int size;

    private Node root;

    //添加元素
    public boolean add(T data) {
        if (size == 0) {
            root = new Node(data, null);
        } else {
            add(data, root);
        }
        size++;
        return true;
    }

    //递归添加子节点
    private void add(T data, Node root) {
        //调用元素的compareto方法,查看根节点和data的大小关系
        int f = root.data.compareTo(data);
        //如果f大于等于0说明节点大,那应该把数据放在根节点左边。
        //如果f小于0说明节点小数据大,应该把数据放到根节点右边
        Node node = f > 0 ? root.left : root.right;
        //如果根节点的左边或者右边==null表示可以直接往根节点放子节点
        if (node == null) {
            //创建子节点
            Node newNode = new Node(data, root);
            if (f > 0) {
                //放到左边
                root.left = newNode;
            } else {
                //放到右边
                root.right = newNode;
            }
        } else {
            //如果根据点的左边或者右边有元素
            //需要将改元素在次重复判断以上全部步骤。
            add(data, node);
        }
    }


    //打印二叉树查看结果
    public Object[] list() {
        ArrayList<T> list = new ArrayList<>();
        toArray(root, list);
        return list.toArray();
    }

    //递归函数
    private void toArray(Node node, ArrayList list) {
        Node left = node.left;
        //如果有左节点
        if (left != null) {
            toArray(left, list);
        }

        System.out.println(node.data);//打印自身节点
        list.add(node.data);
        //如果有右边节点,一样递归
        Node right = node.right;
        if (right != null) {
            toArray(right, list);
        }

        return;
    }

    public int size() {
        return size;
    }


    //判断节点是否存在
    public boolean contains(T data) {
        if (query(data, root) == null) {
            System.out.println("没有找到");
            return false;
        } else {
            System.out.println("一共寻找了" + x + "次");
            return true;
        }
    }

    int x = 0;//记录树的遍历次数,以检验是否是最优解

    //查询节点
    private Node query(T data, Node node) {
        x++;
        //先找到这个元素的位置
        int i = node.data.compareTo(data);
        //如果==0就是找到了。
        if (i == 0) {
            return node;
        }
        Node left = node.left;
        Node right = node.right;
        //向左查询
        if (i > 0 && left != null) {
            return query(data, left);
        } else if (i < 0 || right != null) {//向右查询
            return query(data, right);
        } else {
            return null;
        }
    }

    /*
     * 删除节点有三种情况
     * 1 被删除的节点无子孙节点,直接删除。
     * 2 被删除的节点只有左子节点或者只有右子节点,需要将左或者右子节点顶替被删除的节点。
     * 3 被删除的节点有左子节点或者右子节点,这种情况有两种方案。
     *   1 从左节点族群中找到最右边的子孙节点。
     *   2 从右节点族群中找到最左边的子孙节点。
     * 二叉树的规则是节点左边的小于节点,节点右边的大于或等于节点。所以找到左边的最右边或者右边
     * 的最左边这两个值是最接近于被删除节点的。我这里取值是左边的最右边
     * */
    public T remove(T data) {
        Node removeNode = query(data, root);
        if (removeNode == null) {
            return null;
        }
        //如果只有一个节点,那么肯定是root节点
        if (size == 1) {
            root = null;
        } else if (removeNode.left == null && removeNode.right == null) {//第一种情况
            //拿到父节点,如果当前节点没有父节点也没有子节点,那么size就是1不会走到这里
            Node removeNodeParent = removeNode.parent;
            //判断当前节点在父节点的左边还是右边,如果父节点比被删除节点大
            //表示被删除的节点在父节点的左边,如果相等或者小于那么被删除的
            //元素在父节点的右边
            int c = removeNodeParent.data.compareTo(removeNode.data);
            if (c > 0) {
                //父节点左孩子置为null
                removeNodeParent.left = null;
            } else {
                //父节点右孩子置为null
                removeNodeParent.right = null;
            }
        } else if (removeNode.left == null || removeNode.right == null) {//第二种情况
            //替代被删除的节点
            Node newChild;
            //被删除的节点有左节点族群
            if (removeNode.left != null) {
                //被删除节点的左节点替代被删除的节点
                newChild = removeNode.left;
                //断开被删除节点与子节点的指针
                removeNode.left = null;
            } else {
                //被删除的节点有右节点族群
                //被删除节点的右节点替代被删除的节点
                newChild = removeNode.right;
                //断开被删除节点与子节点的指针
                removeNode.right = null;
            }
            //如果被删除的节点没有父节点,说明这个节点是根节点
            Node removeNodeParent = removeNode.parent;
            //如果不是根节点。
            if (removeNodeParent != null) {
                //判断当前节点在父节点的左边还是右边,如果父节点比被删除节点大
                //表示被删除的节点在父节点的左边,如果相等或者小于那么被删除的
                //元素在父节点的右边
                int c = removeNodeParent.data.compareTo(removeNode.data);
                if (c > 0) {
                    //将替代节点设为父节点的左孩子
                    removeNodeParent.left = newChild;
                } else {
                    //将替代节点设为父节点的右孩子
                    removeNodeParent.right = newChild;
                }
                //断开被删除节点与父节点的指针
                removeNode.parent = null;
            } else {//如果被删除的节点是根节点,那么被删除节点无论是拥有左节点还是右节点这个节点都是根节点
                root = newChild;
                //根节点不需要父节点
                root.parent = null;
            }
        } else {//第三种情况
            Node newChild = queryLeftInRight(removeNode.left);
            //拿到替换节点的父节点
            Node newChildParent = newChild.parent;
            //查询替换节点在它原本父节点的哪边
            int i = newChildParent.data.compareTo(newChild.data);
            //此操作为了将替换节点从原有的父节点删掉。
            if (i > 0) {
                newChildParent.left = null;
            } else {
                newChildParent.right = null;
            }

            //被删除节点的父节点
            Node removeNodeParent = removeNode.parent;

            //被删除的节点不是根节点
            if (removeNodeParent != null) {
                //判断当前节点在父节点的左边还是右边,如果父节点比被删除节点大
                //表示被删除的节点在父节点的左边,如果相等或者小于那么被删除的
                //元素在父节点的右边
                int c = removeNodeParent.data.compareTo(removeNode.data);
                if (c > 0) {
                    removeNodeParent.left = newChild;
                } else {
                    removeNodeParent.right = newChild;
                }
                //如果被删除节点的左节点不是替代节点
                //则需要将被删除节点的左节点交给替代节点的左节点
                if (removeNode.left != newChild) {
                    newChild.left = removeNode.left;
                }
                //被删除元素的右节点交给替代节点的右节点
                newChild.right = removeNode.right;
                //端口被删除节点与子节点和父节点的指针
                removeNode.parent = null;
                removeNode.left = null;
                removeNode.right = null;
            } else {//被删除的是根节点
                //如果根节点的左边节点不是替代节点则
                //将父节点的左边交给替代节点的左边
                if (root.left != newChild) {
                    newChild.left = root.left;
                }
                //将根节点的右子节点交给替代节点的右边
                newChild.right = root.right;
                //替代节点成为根节点
                root = newChild;

            }

        }

        //长度-1
        size--;
        //返回被删除节点的数据
        return removeNode.data;
    }

    //获取左节点族中最右边的节点
    private Node queryLeftInRight(Node node) {
        if (node.right != null) {
            return queryLeftInRight(node.right);
        }
        return node;
    }

    class Node {
        private T data;//节点数据
        private Node left;//左边的孩子
        private Node right;//右边的孩子
        private Node parent;//父节点

        public Node(T data, Node parent) {
            this.data = data;
            this.parent = parent;
        }

        public void setLeft(Node left) {
            this.left = left;
        }

        public void setRight(Node right) {
            this.right = right;
        }
    }
}
View Code

二叉排序树可能会退化程链表

手写二叉排序树(二叉查找树、二叉搜索树)