二叉树之二分搜索树

先来看看二叉树的定义:

我们再来看看二分搜索树:

扫描二维码关注公众号,回复: 2681555 查看本文章

看完基本概念我们来看代码:完成的代码在我github上:https://github.com/yangbishang/Data-Structures,代码上有详细注解;这里我只讲解下思路:

1.二分搜索树的前序中序后序遍历(深度优先遍历)

       前面的图片已经说了,二叉树的每个子树也是二叉树,而一个子节点也是二叉树,所以根据这个思想,我们可以使用遍历来完成遍历,因为遍历二叉树时每个节点都会经过三次,所以遍历也可以分为前、中、后序遍历,即前序是在第一次访问节点时,对节点进行操作,而中后序就是在二三次时对节点进行操作。我们来举个例子,看上面一个图片,已节点50位例:当我们从节点58往下遍历时,我们就来到了50(第一次经过),然后再遍历左子节点42,42没有子节点则跳回到节点50(第二次经过),然后遍历右字节点53,53没有子节点则跳回到50(第三次经过),然后再跳回到58。

代码如下:

    // 二分搜索树的前序遍历 --- 应用:应用最多
    public void preOrder(){
        preOrder(root);
    }
    // 前序遍历以node为根的二分搜索树,递归算法
    private void preOrder(Node node ){
        if(node == null)
            return ;

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

中后序遍历如下:

   // 二分搜索树的中序遍历 --- 应用:排序 等
    public void inOrder(){
        inOrder(root);
    }
    // 中序遍历以Node为根的二分搜索树,递归算法
    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);
    }
    // 后序遍历以Node为根的二分搜索树,递归算法
    public void postOrder(Node node){
        if(node == null)
            return;

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

注意:中序遍历是按从小到大的顺输出的,而前序和后序都不是。

上面的遍历都是用递归来实现的,当然我们也可以不使用递归来实现。这里我们只讨论前序的非递归写法,因为中序和后序遍历的非递归写法较复杂,且用的不多,所以不做过多讨论;

这里我们将用到另一种数据结构:栈;首先我先给出代码:

    // 二分搜索树的非递归前序遍历---利用栈结构(Stack)
    public void preOrderNR(){
        Stack<Node> stack = new Stack<Node>();
        stack.push(root);
        while(!stack.isEmpty()){       //只要栈里面不为空,就把栈里面的节点拿出来输出
            Node cur = stack.pop();
            System.out.println(cur.e);

            if(cur.right != null)        //再把拿出来的节点(cur)的左右节点按右左顺序push进栈
                stack.push(cur.right);
            if(cur.left != null)
                stack.push(cur.left);
        }
    }

可能大家会有点不明白,这里我们给出一个二叉树如下图

       首先我们先把根节点压入栈中,然后判断栈中是否为空(现在当然不为空了),然后取出栈顶元素28输出,再判断节点28是否有左右节点,有的话就先把右节点30压栈,再压左节点,然后再取出栈顶元素16输出,然后在判断16是否有左右节点,有就把右节点22压栈,再压左节点13,然后取出栈顶元素13输出,在判断是否有左右节点,没有就继续取出栈顶元素22输出,判断22是否有左右节点,没有就继续取出栈顶元素30输出,然后压栈30的右节点42,再压左节点29,然后输出左节点29,再输出右节点42.

2.层序遍历(广度优先遍历)

一般二叉树的广度优先遍历不是使用递归来实现,而是借助数据结构队列来实现

    // 二分搜索树的层序遍历(广度优先遍历主要用于搜索策略上)
    // 利用队列结构(Queue)
    public void levelOrder(){
        Queue<Node> queue = new LinkedList();
        queue.add(root);
        while( !queue.isEmpty() ){
            Node node = queue.remove();
            System.out.println(node.e);

            if(node.left != null)
                queue.add(node.left);
            if(node.right != null)
                queue.add(node.right);
        }
    }

看不懂代码我们结合下图来看

我们先把28加入队列,然后拿出来输出,再将28的左节点16入队,再将右节点30入队,然后将队首的16节点拿出来输出,再将16节点的左右节点13和22入队,再将队首的30拿出来输出,然后将左右节点29,42入队,然后再将队首的元素依次取出,即:13,22,29,42

 

 

3.删除操作

3.1删除最小值和最大值

直接看代码吧,这个比较容易

  //寻找二分搜索树的最小元素
    public E minimum(){
        if(size == 0)
            throw new IllegalArgumentException("BST is empty");

        return minimum(root).e;
    }
    //返回以nodfe为根的二分搜索树的最小值所在的节点
    private Node minimum(Node node){
        if(node.left == null)
            return node;

        return minimum(node.left);

    }


    //寻找二分搜索树的最大元素
    public E maximum(){
        if(size == 0)
            throw new IllegalArgumentException("BST is empty");

        return maximum(root).e;
    }
    //返回以node为根的二分搜索树的最大值所在的节点
    private Node maximum(Node node){
        if(node.right == null)
            return node;

        return maximum(node.right);
    }

    /*
     *
     * 删
     *
     */

    // 从二分搜索树中删除最小值所在的节点,返回最小值
    public E removeMin(){
        E ret = minimum();                       //minimum函数已经检查了二叉树是否为空
        root = removeMin(root);
        return ret;
    }
    // 删除掉以node为根的二分搜索树中的最小节点       // 返回删除节点后新的二分搜索树的根
    private Node removeMin(Node node){
        if(node.left == null){                     // node.left为空,说明node为最小节点
            Node rightNode = node.right;           // 不管node.right为不为空,让rightNode指向node.right返回回去就行了
            node.right = null;                     // node的右指针为空,然后GB回收node
            size -- ;
            return rightNode;
        }
        node.left = removeMin(node.left);          //如果左子树还有,就继续
        return node;
    }


    //从二分搜索树中删除最大值所在的节点,返回最大值
    public E removeMax(){
        E ret = maximum();                            //maximum函数已经检查了二叉树是否为空
        root = removeMax(root);
        return ret;
    }
    // 删除掉以node为根的二分搜索树中的最大节点,返回删除节点后新的二分搜索树的根
    private Node removeMax(Node node){
        if(node.right == null){                 // node.right为空,说明node为最大节点
            Node leftNode = node.left;           // 让leftNode指向node.left,然后局返回leftNode
            node.left = null;                  
            size -- ;
            return leftNode;
        }
        node.right = removeMax(node.right);
        return node;
    }

3.1删除任意一个节点

在二叉树中删除一个只有做孩子的节点和删除一个只有右孩子的节点都比较好办,与删除最大值最小值类似,而删除左右都有孩子的节点才是比较复杂的

1962年,Hibbard提出了Hibbard Deletion,我们来具体看下怎么实现

现在我们要删除的58左右都有节点,左子树所有的节点都比58小,右子树所有的节点都比58大,我们删掉58后要找个数放到58的位置,这个数就是58的后继(比58大,且与58大小相差最近的的值),怎样找到这个值呢,很简单,就是58右子树中最小值59 。(当然我们也可以找58的前驱,即58左子树中的最大值

    // 从二分搜索树中删除元素为e的节点
    public void remove(E e){
        root = remove(root, e);
    }
    // 删除掉以node为根的二分搜索树中值为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{   // e.compareTo(node.e) == 0

            // 待删除节点左子树为空的情况
            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 = minimum(node.right);
            successor.right = removeMin(node.right);     //removeMin(node.right) 返回的是删除了后继节点后 新的node.right这歌节点
            size ++ ;                                     //注意removeMin函数中执行了size--,但实际上最小值并没有删掉,而是替换成了successor,所以要加
            successor.left = node.left;
            node.left = node.right = null;
            size --;

            return successor;
        }
    }

完整的代码在我github上https://github.com/yangbishang/Data-Structures/tree/master/05-Binary-Search-Tree

猜你喜欢

转载自blog.csdn.net/qq_36582604/article/details/81505785