二叉树练习(四)— 用先序数组和中序数组重建一棵树

题目:
给定两个数组,其中元素为一颗树用先序遍历和中序遍历的结果。利用两个数组,来构建一颗新树。

在这里插入图片描述
假设二叉树如上图。
先序数组:头左右的顺序 [1,2,4,5,3,6,7]
中序数组:左头右的顺序 [4,2,5,1,6,3,7]
递归函数f:由先序数组pre、数组范围0 ~ 6和中序数组in 数组范围 0 ~ 6 构建新的树。并返回head节点 f(pre,0,6,in,0,6)

分析:

  1. 新树的head节点是谁? 当然是先序数组的第一个元素。
  2. 确定了head节点后,左树怎么建?
    先序返回的顺序是,头左右,中序返回的顺序是左头右,所以确定了先序数组的第一个元素为新树的head后,那么它后面的3个节点 加上 根据新树head节点在中序中找到对应节点(head节点在中序数组中的位置)的左端,就是新树的左侧节点。如果调用递归函数的话,参数就应该是f(pre,1,3,in,0,2),返回后的结果,则是新树的左侧节点。
  3. 右数如何建立?
    上一步操作已经确定了左侧节点,那先序数组4 ~ 6以及中序数组的 4 ~ 6的索引位置,就是新树的右侧节点。调用f(pre,4,6,in,4,6)函数构建后返回即可。
  4. 给定两个数组中,如果有一个为null,则不需构建新树。
  5. 如果元素中仅有一个元素,说明二叉树中只有一个节点,那么先序和中序是一样的,直接构建新树后return。
  6. 如果先序数组长度和中序数组长度不相同,同样return。
  7. 如何确定左右节点长度范围?由于已经确定了先序数组中第一个元素为新树head节点
    7.1 根据先序head节点,找到中序数组中该节点位置,find。
    7.2 先序数组中head节点 + find - 中序数组中前面元素的数量 ,就可以确定了先序数组和中序数组左右侧的范围。
    7.3 举例:先序数组位置从 5,6,7,8,9,10 中序数组位置从 13,14,15,16,17,18 一样长度6个元素

如果,此时find位置在16的元素和先序数组中5位置元素相同,那16左侧13 ~ 15 3个位置就应该是左侧节点,那先序数组左侧 就应该是 16 - 13 + 5 = 8,所以先序数组 6 ~ 8的位置就应该是左侧节点。
确定了左右侧树的节点后,递归调用f函数,调整数组范围参数即可。

代码实现:

 public static class TreeNode {

        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int val) {
            this.val = val;
        }
    }

    public static TreeNode buildTree(int[] pre, int[] in) {
        if (pre == null || in == null || pre.length != in.length) {
            return null;
        }
        return f(pre, 0, pre.length - 1, in, 0, in.length - 1);
    }

    //一棵树,先序数组 [L1 ... R1] 中序数组 [L2 ... R2]
    //构建新树,返回head节点
    public static TreeNode f(int[] pre, int L1, int R1, int[] in, int L2, int R2) {
		
		 //如果树的结构是 1的right是2  2的right是3,
        //那么先序结果就是[1,2,3] 中序的结果也是[1,2,3]
        //那么在中序数组找到find是第一个位置,左侧没东西了

        //或者 结构是 1的left是2 2的right是3
        //先序[1,2,3] 中序 [2,3,1]  中序的右边没东西了,数组越界。
        if (L1 > R1) {
            return null;
        }

        TreeNode head = new TreeNode(pre[L1]);
        //说明长度为1,数组内只有一个元素
        if (L1 == R1) {
            return head;
        }

        int find = L2;
        //找出中序中的find节点,确认左右树范围
        while (in[find] != pre[L1]) {
            find++;
        }

        head.left = f(pre, L1 + 1, L1 + find - L2, in, L2, find - 1);
        head.right = f(pre, L1 + find - L2 + 1, R1, in, find + 1, R2);
        return head;
    }
    
    public static void pre(TreeNode node, List preList) {

        if (node == null) {
            return;
        }
        preList.add(node.val);
        pre(node.left,preList);
        pre(node.right,preList);
    }

    public static void in(TreeNode node, List inList) {

        if (node == null) {
            return;
        }
        pre(node.left,inList);
        inList.add(node.val);
        pre(node.right,inList);
    }
    	
      public static void printTree(TreeNode node){
        if (node == null) {
            return;
        }
        System.out.println(node.val);
        printTree(node.left);
        printTree(node.right);
    }

	  public static void main(String[] args) {
        TreeNode node = new TreeNode(1);
        node.left = new TreeNode(2);
        node.right = new TreeNode(3);

        node.left.left = new TreeNode(4);
        node.left.right = new TreeNode(5);

        node.right.left = new TreeNode(6);
        node.right.right = new TreeNode(7);

        List<Integer> preList = new ArrayList();
        List<Integer> inList = new ArrayList();
        pre(node,preList);
        in(node,inList);
        int[] preArr = preList.stream().mapToInt(i -> i).toArray();
        int[] inArr = inList.stream().mapToInt(i -> i).toArray();

        TreeNode treeNode = buildTree(preArr, inArr);
        printTree(treeNode);
    }

部分代码优化:之前代码都会在每一次递归中遍历的寻找find,那直接将中序数组中的值放进map中,从map中获取即可。

public static TreeNode buildTree(int[] pre, int[] in) {
        if (pre == null || in == null || pre.length != in.length) {
            return null;
        }

        HashMap<Integer, Integer> valueIndex = new HashMap<>();
        for (int i = 0; i < in.length; i++) {
            valueIndex.put(in[i],i);
        }
        return f(pre, 0, pre.length - 1, in, 0, in.length - 1,valueIndex);
    }

    //一棵树,先序数组 [L1 ... R1] 中序数组 [L2 ... R2]
    //构建新树,返回head节点
    public static TreeNode f(int[] pre, int L1, int R1, int[] in, int L2, int R2, HashMap<Integer, Integer> valueIndex) {

        //如果树的结构是 1的right是2  2的right是3,
        //那么先序结果就是[1,2,3] 中序的结果也是[1,2,3]
        //那么在中序数组找到find是第一个位置,左侧没东西了

        //或者 结构是 1的left是2 2的right是3
        //先序[1,2,3] 中序 [2,3,1]  中序的右边没东西了,数组越界。
        if (L1 > R1) {
            return null;
        }

        TreeNode head = new TreeNode(pre[L1]);
        //说明长度为1,数组内只有一个元素
        if (L1 == R1) {
            return head;
        }

        int find = valueIndex.get(L1);

        head.left = f(pre, L1 + 1, L1 + find - L2, in, L2, find - 1,valueIndex);
        head.right = f(pre, L1 + find - L2 + 1, R1, in, find + 1, R2,valueIndex);
        return head;
    }

总结:

  1. 递归算法如果条理不清晰,可先将先序、中序等整体步骤,每个节点经过几次画出来。
  2. 上述代码,第一次1是head,并找到左右分界范围。
  3. 第二次head.left时,根据范围会查找左侧的head和right的head,以此类推。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值