《玩转数据结构 从入门到进阶》二分搜索树 Binary Search Tree

本文来源于liuyubobobo的“玩转数据结构 从入门到进阶”视频教程

上图是一个Binary Search Tree,因汉字数量庞大,就有了很多种翻译,如:二分搜索树、二叉搜索树、……………

二分搜索树以二叉树为基础,但多了两个特点:

1、二分搜索树节点的值具有可比较性。

2、每个节点都比它左子树的任意元素大,而且比右子树的任意元素小(此文不讨论有重复元素的树)。

下面使用java代码实现Binary Search Tree的增加、查找、删除操作。

定义节点Node类

private class Node {
    // 节点值
    public E e;
    // 每个节点都可能有左节点、右节点。最后一层的节点没有左右节点可以视为left、right都为null
    public Node left, right;

    public Node(E e) {
        this.e = e;
        left = null;
        right = null;
    }
}

树的代码实现

//二分搜索树
public static class BinarySearchTree<E extends Comparable<E>> {
    private class Node {
        // 节点值,二分搜索树的值是有比较性的,所以泛型E必须是Comparable的子类
        public E e;
        // 每个节点都可能有左节点、右节点。最后一层的节点没有左右节点可以视为left、right都为null
        public Node left, right;

        public Node(E e) {
            this.e = e;
            left = null;
            right = null;
        }
    }

    // 树使用root节点作为索引,增查删都从root开始操作
    private Node root;
    // 树中节点的数量,即树的大小
    private int size;

    public BinarySearchTree() {
        root = null;
        size = 0;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    // 在此添加增查删方法
}

先说下递归,递归实际上是一种把大问题、大数据拆成相同类型的小问题、小数据的算法。

而树具有天然的递归性。看上图,蓝色框内的树就是整棵树的缩小版,所以说树具有天然的递归性。

树新增节点代码

        // 向二分搜索树中添加新的元素e
        public void add(E e){
            // 第一次添加的是根节点
            if(root == null){
                root = new Node(e);
                size ++;
            }else{
                add(root, e);  //递归添加元素
            }
        }

        // 向以node为根的二分搜索树中插入元素e,递归算法
        private void add(Node node, E e){
            // 不考虑相同元素的情况
            if(e.equals(node.e)){
                return;
            }else if(e.compareTo(node.e) < 0 && node.left == null){
                // 新增元素比当前节点小,且当前节点左边无节点,新元素插入左边
                node.left = new Node(e);
                size ++;
                return;
            }else if(e.compareTo(node.e) > 0 && node.right == null){
                // 新增元素比当前节点大,且当前节点右边无节点,新元素插入右边
                node.right = new Node(e);
                size ++;
                return;
            }

            // 新增元素比当前节点小,且当前节点左边有节点,递归处理当前元素左边的节点
            if(e.compareTo(node.e) < 0)
                add(node.left, e);
            else{
                //e.compareTo(node.e) > 0 的情况
                // 新增元素比当前节点大,且当前节点右边有节点,递归处理当前元素右边的节点
                add(node.right, e);
            }
        }

上面新增节点的代码容易理解,但代码量较多,可以精简,如下:

        public void add(E e){
            // 使用root为索引
            root = add(root, e);
        }

        public Node add(Node node, E e){
            if (node == null){
                // 第一次添加元素,root就为new Node(e)
                size++;
                return new Node(e);
            }

            if (e.compareTo(node.e) < 0){
                // 在节点左边添加节点
                node.left = add(node.left, e);
            }else if (e.compareTo(node.e) > 0){
                // 在节点右边添加节点
                node.right = add(node.right, e);
            }
            return node;
        }

遍历节点

上图的方式遍历方式,称为前序遍历

        // 前序遍历
        public void preOrder(){
            preOrder(root);
        }

        public void preOrder(Node node){
            if (node == null){
                return;
            }
            // 打印当前元素值
            System.out.println(node.e);
            preOrder(node.left);
            preOrder(node.right);
        }

中序遍历、后序遍历

        // 中序遍历结果从大到小排序
        public void inOrder(){
            inOrder(root);
        }

        public void inOrder(Node node){
            if (node == null){
                return;
            }

            inOrder(node.left);
            System.out.println(node.e);
            inOrder(node.right);
        }

        // 后续遍历,先遍历父节点的子节点,在遍历父节点
        public void postOrder(){
            postOrder(root);
        }

        public void postOrder(Node node){
            if (node == null){
                return;
            }
            postOrder(node.left);
            postOrder(node.right);
            System.out.println(node.e);
        }

写个main函数测试下

       public static void main(String[] args) {
            BinarySearchTree<Integer> bts = new BinarySearchTree<>();
            int[] nums = {28, 16, 30, 22, 13, 42, 29};
            for (int num:nums){
                bts.add(num);
            }
            System.out.println("-------前序遍历------");
            bts.preOrder();
            System.out.println("------中序遍历-------");
            bts.inOrder();
            System.out.println("------后序遍历-------");
            bts.postOrde
        }

前序、后续、中序都属于深度优先,下面实现广度优先遍历

先遍历28,再遍历16、30,最后遍历13、22、29、42。可以通过多加一个队列来实现广度优先遍历。

流程如上图所示

1、28入队

2、28出队,并且若左、右节点不为空,把28左右、节点(16、30)入队

3、16出队,并且若左、右节点不为空,把16左、右节点(13、22入队)

4、30出队,并且若左、右节点不为空,把30左、右节点(29、42入队)

代码实现如下:

        // 广度优先遍历
        public void levelOrder(){
            if (root == null){
                return;
            }
            Queue<Node> q = new LinkedBlockingQueue<>();
            q.add(root);
            while (!q.isEmpty()){
                Node node = q.remove();
                System.out.println(node.e);
                if (node.left != null)
                    q.add(node.left);
                if (node.right != null)
                    q.add(node.right);
            }
        }

查找树的最小值、最大值。当树最左边节点的node.left == null,则node值最小。当树最右边节点的node.right== null,则node值最大。

查找最小值的方式如上图。查找最大值方式也类似,就是在最右边线上查找

// 最小值元素
public E minimum(){
    if(size == 0)
        throw new IllegalArgumentException("空树");
    Node minNode = minimum(root);
    return minNode.e;
}

// 最小值节点
private Node minimum(Node node){
    if( node.left == null )
        return node;
    return minimum(node.left);
}

// 最大值元素
public E maximum(){
    if(size == 0)
        throw new IllegalArgumentException("空树");
    return maximum(root).e;
}

// 最大值节点
private Node maximum(Node node){
    if( node.right == null )
        return node;
    return maximum(node.right);
}

删除最小值

        public E removeMin(){
            // 查找最小值
            E ret = minimum();
            // 删除最小节点
            root = removeMin(root);
            // 返回最小值
            return ret;
        }

        public Node removeMin(Node node){
            // node.left == null,node必然是最小节点
            if (node.left == null){
                // 先保存最小节点的right
                Node nodeRight = node.right;
                // 再把最小节点的right指向null,以便java虚拟机回收最小节点内存
                node.right = null;
                size--;
                return nodeRight;
            }
            // node.left != null,递归node.left
            node.left = removeMin(node.left);
            return node;
        }

删除最大值,只给出代码,不画图了。请参考删除最小值的逻辑

        public E removeMax(){
            E ret = maximum();
            root = removeMax(root);
            return ret;
        }

        private Node removeMax(Node node){
            // node.right == null,节点必然是最大节点
            if(node.right == null){
                // 保存最大节点的right
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }
            // node.right != null,递归node.right
            node.right = removeMax(node.right);
            return node;
        }

删除任意值,情况比较多,如下:

1、节点左孩子为null,就是删除最小值。

2、节点右孩子为null,就是删除最大值。

3、节点左右孩子都有值,使用Hibbard Deletion算法删除值。

Hibbard Deletion在1962年被一个叫做 Hibbard 的计算机科学家提出,这算法简单来说就是这样:假设这个被删除的节点是 d,d 既有左孩子,又有右孩子,可以使用d右子树中的最小值替换d。

        public void remove(E e){
            remove(root, e);
        }

        private Node remove(Node node, E e){
            if (node == null)
                return null;
            if (e.compareTo(node.e) < 0){
                // 递归左侧节点
                node.left = remove(node.left, e);
                return node;
            }else if (e.compareTo(node.e) > 0){
                // 递归右侧节点
                node.right = remove(node.right, e);
                return node;
            }else {
                // 删除最小值
                if (node.left == null){
                    Node rightNode = node.right;
                    node.right = null;
                    size--;
                    return rightNode;
                }
                // 删除最大值
                if (node.right == null){
                    Node leftNode = node.left;
                    node.left=null;
                    size--;
                    return leftNode;
                }

                //当前node有左右孩子,找出右树中的最小节点successor
                Node successor = minimum(node.right);
                // successor.right = 删除当前node最小值后的树
                successor.right = removeMin(node.right);
                // successor.left = 当前node节点的left
                successor.left = node.left;
                node.left = node.right = null;
                // 返回successor
                return successor;
            }
        }

所有代码

//二分搜索树
public class BinarySearchTree<E extends Comparable<E>> {
    private class Node {
        // 节点值
        public E e;
        // 每个节点都可能有左节点、右节点。最后一层的节点没有左右节点可以视为left、right都为null
        public Node left, right;

        public Node(E e) {
            this.e = e;
            left = null;
            right = null;
        }
    }

    // 树使用root节点作为索引,增查删都从root开始操作
    private Node root;
    // 树中节点的数量,即树的大小
    private int size;

    public BinarySearchTree() {
        root = null;
        size = 0;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void add(E e){
        // 使用root为索引
        root = add(root, e);
    }

    public Node add(Node node, E e){
        if (node == null){
            // 第一次添加元素,root就为new Node(e)
            size++;
            return new Node(e);
        }

        if (e.compareTo(node.e) < 0){
            // 在节点左边添加节点
            node.left = add(node.left, e);
        }else if (e.compareTo(node.e) > 0){
            // 在节点右边添加节点
            node.right = add(node.right, e);
        }
        return node;
    }

    // 前序遍历
    public void preOrder(){
        preOrder(root);
    }

    public void preOrder(Node node){
        if (node == null){
            return;
        }
        // 打印当前元素值
        System.out.println(node.e);
        preOrder(node.left);
        preOrder(node.right);
    }

    // 中序遍历,结果从小到大排序
    public void inOrder(){
        inOrder(root);
    }

    public void inOrder(Node node){
        if (node == null){
            return;
        }

        inOrder(node.left);
        System.out.println(node.e);
        inOrder(node.right);
    }

    // 后序遍历,先遍历父节点的子节点,在遍历父节点
    public void postOrder(){
        postOrder(root);
    }

    public void postOrder(Node node){
        if (node == null){
            return;
        }
        postOrder(node.left);
        postOrder(node.right);
        System.out.println(node.e);
    }

    // 广度优先遍历
    public void levelOrder(){
        if (root == null){
            return;
        }
        Queue<Node> q = new LinkedBlockingQueue<>();
        q.add(root);
        while (!q.isEmpty()){
            Node node = q.remove();
            System.out.println(node.e);
            if (node.left != null)
                q.add(node.left);
            if (node.right != null)
                q.add(node.right);
        }
    }

    // 最小值元素
    public E minimum(){
        if(size == 0)
            throw new IllegalArgumentException("空树");
        Node minNode = minimum(root);
        return minNode.e;
    }

    // 最小值节点
    private Node minimum(Node node){
        if( node.left == null )
            return node;
        return minimum(node.left);
    }

    // 最大值元素
    public E maximum(){
        if(size == 0)
            throw new IllegalArgumentException("空树");
        return maximum(root).e;
    }

    // 最大值节点
    private Node maximum(Node node){
        if( node.right == null )
            return node;
        return maximum(node.right);
    }

    public E removeMin(){
        // 查找最小值
        E ret = minimum();
        // 删除最小节点
        root = removeMin(root);
        // 返回最小值
        return ret;
    }

    public Node removeMin(Node node){
        // node.left == null,node必然是最小节点
        if (node.left == null){
            // 先保存最小节点的right
            Node nodeRight = node.right;
            // 再把最小节点的right指向null,以便java虚拟机回收最小节点内存
            node.right = null;
            size--;
            return nodeRight;
        }
        // node.left != null,递归node.left
        node.left = removeMin(node.left);
        return node;
    }

    public E removeMax(){
        E ret = maximum();
        root = removeMax(root);
        return ret;
    }

    private Node removeMax(Node node){
        // node.right == null,节点必然是最大节点
        if(node.right == null){
            // 保存最大节点的right
            Node leftNode = node.left;
            node.left = null;
            size --;
            return leftNode;
        }
        // node.right != null,递归node.right
        node.right = removeMax(node.right);
        return node;
    }

    public void remove(E e){
        remove(root, e);
    }

    private Node remove(Node node, E e){
        if (node == null)
            return null;
        if (e.compareTo(node.e) < 0){
            // 递归左侧节点
            node.left = remove(node.left, e);
            return node;
        }else if (e.compareTo(node.e) > 0){
            // 递归右侧节点
            node.right = remove(node.right, e);
            return node;
        }else {
            // 删除最小值
            if (node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size--;
                return rightNode;
            }
            // 删除最大值
            if (node.right == null){
                Node leftNode = node.left;
                node.left=null;
                size--;
                return leftNode;
            }

            //当前node有左右孩子,找出右树中的最小节点successor
            Node successor = minimum(node.right);
            // successor.right = 删除当前node最小值后的树
            successor.right = removeMin(node.right);
            // successor.left = 当前node节点的left
            successor.left = node.left;
            node.left = node.right = null;
            // 返回successor
            return successor;
        }
    }

    public static void main(String[] args) {

        _5_BinarySearchTree.BinarySearchTree<Integer> bst = new _5_BinarySearchTree.BinarySearchTree<>();
        int[] nums = {28, 16, 30, 22, 13, 42, 29};
        for (int num:nums){
            bst.add(num);
        }

        System.out.println("-------前序遍历------");
        bst.preOrder();
        System.out.println("------中序遍历-------");
        bst.inOrder();
        System.out.println("------后序遍历-------");
        bst.postOrder();
        System.out.println("------广度优先遍历-------");
        bst.levelOrder();


        ArrayList<Integer> nums2 = new ArrayList<>();

        //while(!bst.isEmpty())
        //    nums2.add(bst.removeMin());
        //System.out.println(nums2);

        //while(!bst.isEmpty())
        //    nums2.add(bst.removeMax());
        //System.out.println(nums2);


        //bst.remove(16);
        //bst.preOrder();

    }

}
发布了51 篇原创文章 · 获赞 14 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/u010606397/article/details/98464575