根据后续遍历结果重建搜索二叉树(Java实现)

题目:
已知一个搜索二叉树后序遍历的数组posArr,请根据posArr,重建出整棵树,并返回头结点

先来了解后序遍历是个什么玩意
二叉树的前序、中序、后序三种遍历方式(Java实现)

根据后序遍历的性质,我们可以知道,一个搜索二叉树如果长成下面这个鸟样
在这里插入图片描述
那么他后序遍历的结果就是下面这个数组

3,5,4,7,6,9,11,10,8

分析:

  • 后序遍历一定是最后遍历根节点,那么可以推出根节点的位置一定是数组的最后一位,上面的数组我们可以确定根节点为8
  • 同时根据后序遍历的性质,我们可以知道,一定是先遍历完左子树才会去遍历右子树,那么结果中的前半部分肯定是左子树了,后半部分是右子树(不包含最后一位,最后一位是根)
  • 如何确定哪部分是左子树哪部分是右子树呢?题目是二叉搜索树,根据二叉搜索树的性质,根节点左边的所有元素必定小于根节点,根节点右边的所有元素必定大于根节点,那从数组起始位置找到最后一个小于根节点的元素的位置,假设位置为n,0···n之间的元素就是根节点左子树的所有元素,n到根节点前面一位的元素就是右子树的所有元素
  • 并且二叉树的左右子树也都满足后序遍历的性质,那么我们将数组分为左右两部分之后,可以将切分后的两个子数组分别视为两个完整的后序遍历的结果

那么经过上面的推理,我们大概有了下面的思路
1、先取出根节点
2、将剩余数组切分为左右两个子树的后序遍历的结果
3、再分别对切分的数组进行建树的操作,直到数组切分的只有一个元素了,直接返回
4、将根节点的左右子节点指针指向上一步建好的树的根节点
那么根据题意,我们可以想到

先定义节点,二叉树的节点不必多说了

static class Node<T>{
    private Node left;
    private Node right;
    private T data;
    public Node(T t){
        this.data = t;
    }
}

根据上面的思路写出如下方法
代码:

public class PosArr {

    static class Node<T>{
        private Node left;
        private Node right;
        private T data;
        public Node(T t){
            this.data = t;
        }
    }

    private static Node head;

    /**
     * 最基本的重建树的方法
     * @param arr
     * @param left
     * @param right
     * @return
     */
    private static Node process(int[] arr, int left, int right){
        if(left > right){
            return null;
        }
        Node head = new Node(arr[right]);
        if(left == right){
            return head;
        }
        int mid = left - 1;
        for (int i = left; i < right; i++){
            if(arr[i] < arr[right]){
                mid = i;
            }
        }
        //递归建立左子树和右子树
        head.left = process(arr, left, mid);
        head.right = process(arr, mid + 1, right - 1);

        return head;
    }

    public static Node posArray2BST(int arr[]){
        head = process(arr, 0, arr.length - 1);
        return head;
    }
}

上面的查找最后一个小于根节点的值使用的遍历的方式,我们还可以将其优化成二分查找的方式,这个二分查找和有序的二分查找不一样,我们需要有一个标记位来记录,当查找到的数值小于根节点的时候,记录一次,直到二分到不能再分的时候,标记位记录的就是左子树的最后一个节点,如果二叉树记录不平衡,比如只有右子树,那么标记位一次都不会记录,为根节点前面数组的起始位置,有了这个思路就可以开始写代码了

    /**
     * 优化后的重建树的方法(使用二分查找)
     * @param arr 要建树的数组
     * @param left 数组的最左端
     * @param right 数组的最右端
     * @return
     */
    private static Node process2(int[] arr, int left, int right){
        if(left > right){
            return null;
        }
        Node head = new Node(arr[right]);
        if(left == right){
            return head;
        }
        int mid = left - 1;//mid为标记位,如果没有左子树,那么建立左树的时候就会返回null
        //二分查找
        int l = left,r = right - 1;
        while (l <= r){
            int m = l + ((r - l) >> 1);
            if(arr[m] < arr[right]){
                mid = m;
                left = m + 1;
            }else {
                right = m - 1;
            }
        }

        //递归建立左子树和右子树
        head.left = process2(arr, left, mid);
        head.right = process2(arr, mid + 1, right - 1);
        return head;
    }

二分真是一个好东西,好多东西我们都可以使用二分来优化

至此

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值