首页 > 其他分享 >9、二叉树

9、二叉树

时间:2023-02-20 17:45:56浏览次数:47  
标签:return int 节点 二叉树 root public left

1、定义

二叉树(binary tree)是指树中节点的度不大于2的有序树
①在二叉树中,一个节点最多有两颗子树,二叉树节点的度<=2
②二叉树的有左右之分,且子树的次序不能颠倒,因此二叉树是有序树

2、二叉树的遍历操作

对于二叉树来说,一共有四种遍历方式
①深度优先遍历(dfs):先序遍历(根左右),中序遍历(左根右),后序遍历(左右根)
②广度优先遍历(bfs):层序遍历(将二叉树层层访问)

2.1、先序遍历

先访问根节点,然后递归访问左子树,在递归访问右子树(根左右,就是第一次访问该节点就打印,并且第一个节点一定是根节点)FCADBEHGM

2.2、中序遍历

先递归访问根的左子树,然后是根节点,最后是右子树(左根右,就是第二次访问到该节点时就打印,左子树节点都在根节点左侧,右子树节点在根节点右侧)ACBDFHEMG

2.3、后续遍历

先递归访问左子树,在递归访问右子树,最后是根节点(左右根,就是第三次访问到该节点时就打印)ABDCHMGEF

2.4、层序遍历

FCEADHGBM

2.5、代码实现如下

点击查看代码
public class TraversalBinaryTree {
    private static class Node<V> {
        public V value;
        public Node<V> left;
        public Node<V> right;
        public Node(V val) {
            this.value = val;
        }
    }

    // 先序遍历
    public static void pre(Node head) {
        if (head == null) {
            return;
        }
        System.out.print(head.value + " ");
        pre(head.left);
        pre(head.right);
    }

    // 中序遍历
    public static void in(Node head) {
        if (head == null) {
            return;
        }
        in(head.left);
        System.out.print(head.value + " ");
        in(head.right);
    }

    // 后序遍历
    public static void pos(Node head) {
        if (head == null) {
            return;
        }
        pos(head.left);
        pos(head.right);
        System.out.print(head.value + " ");
    }

    public static void main(String[] args) {
        Node head = new Node("F");
        Node c = new Node("C");
        Node e = new Node("E");
        head.left = c;
        head.right = e;
        c.left = new Node("A");
        c.right = new Node("D");
        e.left = new Node("H");
        e.right = new Node("G");
        c.right.left = new Node("B");
        e.right.left = new Node("M");
        // 先序遍历
        pre(head);
        System.out.println("\n====================");
        // 中序遍历
        in(head);
        // 后序遍历
        System.out.println("\n====================");
        pos(head);
    }
}

2.6、结论递归序

即每一个节点都会经过3次,不同的打印时机,不同的遍历方式

如果在3个地方都加上打印则得到结果:
F C A A A C D B B B D D C F E H H H E G M M M G G E F
1 2 2 2 1 3 3 3 1
以二叉树各个节点没有重复值为例:首次出现的集合即为(先序遍历)

public static void f(Node head) {
    if(head ==null)
      return;
    // 1
    f(head.left);
    // 2
    f(head.right);
    // 3
}

3、常见Coding题

public class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;
    TreeNode() {}
    TreeNode(int val) {
        this.val = val;
    }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

3.1、判断两颗二叉树是否相同

测试链接:https://leetcode.com/problems/same-tree
给定两棵二叉树 p 和 q 的根,编写一个函数来检查它们是否相同。 如果两个二叉树在结构上相同,并且节点具有相同的值,则认为它们是相同的。


public class SameTree {
    public static boolean isSameTree(TreeNode p, TreeNode q) {
        // 一个为空,一个不为空
        if (p == null ^ q == null) {
            return false;
        }
        if (p == null && q == null) {
            return true;
        }

        // 根节点相同,与左子树相同,与右子树相同
        return p.val == q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
}

3.2、判断一颗树是否是镜面树

将一棵树从中间拆分为左右两颗树:

  • 根节点天生镜像
  • 左子树的左节点 = 对应右子树的右节点
  • 左子树的右节点 = 对应右子树的左节点



测试链接:https://leetcode.com/problems/symmetric-tree

public class SymmetricTree {
    /**
     * 将一棵树从中间拆分为左右两颗树
     * @param root 树的根节点
     * @return true / false
     */
    public static boolean isSymmetric(TreeNode root) {
        return isMirror(root, root);
    }

    private static boolean isMirror(TreeNode h1, TreeNode h2) {
        if (h1 == null ^ h1 == null) {
            return false;
        }
        if (h1 == null && h2 == null) {
            return true;
        }

        // 根节点天生镜像,左子树的左节点 = 对应右子树的右节点,左子树的右节点 = 对应右子树的左节点
        return h1.val == h2.val && isMirror(h1.left, h2.right) && isMirror(h1.right, h2.left);
    }
}

3.3、计算一颗树的最大深度

给定二叉树的根,返回其最大深度。 二叉树的最大深度是从根节点到最远叶节点的最长路径上的节点数。
左节点最大深度,与右节点最大深度取最大值,加上根节点

测试链接:https://leetcode.com/problems/maximum-depth-of-binary-tree

public class MaximumDepthOfBinaryTree {
    public static int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        // 左,右取Max + 根
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
        // int left = maxDepth(root.left);
        // int right = maxDepth(root.right);
        // int ans = Math.max(left, right) + 1;
        // return ans;
    }

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

        int depth = maxDepth(root);
        System.out.println(depth);
    }
}

3.4、根据二叉树的先序和中序数组,重建二叉树

给定两个整数数组 preorder 和 inorder(无重复值),其中 preorder 是二叉树的前序遍历,inorder 是同一棵树的中序遍历,构造并返回二叉树。

3.4.1、分析

找到中序数组in[]中的根节点位置find
中序:find的左边即左树,右边即为右树
先序:L1的下一个位置开始,数(find - L2)个数即左树,剩下的即右树

3.4.2、解决越界

image

3.4.3、代码实现

测试链接:https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal

点击查看代码
public class ConstructBinaryTreeFromPreorderAndInorderTraversal {

    public static TreeNode buildTree(int[] pre, int[] in) {
        if (pre == null || in == null || pre.length != in.length) {
            return null;
        }
        Map<Integer, Integer> valueIndexMap = new HashMap<>();
        int inLen = in.length;
        for (int i = 0; i < inLen; i++) {
            valueIndexMap.put(in[i], i);
        }

        // return doBuild1(pre, 0, pre.length - 1, in, 0, inLen - 1);
        // 优化
        return doBuild(pre, 0, pre.length - 1, in, 0, inLen - 1, valueIndexMap);
    }

    /**
     * 优化,将中序数组的遍历抽取为 Map<value, index>
     */
    public static TreeNode doBuild(int[] pre, int L1, int R1, int[] in, int L2, int R2, Map<Integer, Integer> valueIndexMap) {
        // 范围错过去,就是没有树
        if (L1 > R1) {
            return null;
        }
        TreeNode head = new TreeNode(pre[L1]);
        if (L1 == R1) {
            return head;
        }

        // 在中序数组中找到根节点
        int find = valueIndexMap.get(pre[L1]);

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

    /**
     * 有一棵树,先序结果是pre[L1...R1],中序结果是in[L2...R2]
     *
     * @param pre 先序数组
     * @param L1  先序数组左下标
     * @param R1  先序数组右下标
     * @param in  中序数组
     * @param L2  中序数组左下标
     * @param R2  中序数组右下标
     * @return 重建树的根节点
     */
    public static TreeNode doBuild1(int[] pre, int L1, int R1, int[] in, int L2, int R2) {
        // 范围错过去,就是没有树
        if (L1 > R1) {
            return null;
        }
        TreeNode head = new TreeNode(pre[L1]);
        if (L1 == R1) {
            return head;
        }

        // 遍历:在中序数组中找到根节点
        int find = L2;
        while (pre[L1] != in[find]) {
            find++;
        }
        // L1   ...     R1
        // L2 ..find..  R2
        head.left = doBuild1(pre, L1 + 1, L1 + find - L2, in, L2, find - 1);
        head.right = doBuild1(pre, L1 + find - L2 + 1, R1, in, find + 1, R2);
        return head;
    }
}

3.5、二叉树按层遍历并收集节点

给定二叉树的根,返回其节点值的自下而上的层次顺序遍历。 (即,从左到右,从叶到根逐层)。

3.5.1、代码实现

由于要自下而上的层次顺序遍历,所以使用链表,在任何位置插入O(1)
使用队列先进先出,从左到右收集

测试链接:https://leetcode.com/problems/binary-tree-level-order-traversal-ii

public class BinaryTreeLevelOrderTraversalII {
    public static List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> res = new LinkedList<>();
        if (root == null) {
            return res;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Integer> curRes = new LinkedList<>();
            for (int i = 0; i < size; i++) {
                TreeNode curNode = queue.poll();
                curRes.add(curNode.val);

                if (curNode.left != null) {
                    queue.add(curNode.left);
                }
                if (curNode.right != null) {
                    queue.add(curNode.right);
                }
            }
            res.add(0, curRes);
        }
        return res;
    }
}

3.6、判断是否是平衡二叉搜索树(AVL树)

平衡二叉搜索树(Balanced Binary Search Tree)是一种结构平衡的[二叉搜索树] ,它是一种每个节点的左右两子[树] 高度差都不超过一的[二叉树] 。它能在O(logn)内完成插入、查找和删除操作,最早被发明的平衡二叉搜索树为[AVL树] 。

3.6.2、代码实现

测试链接:https://leetcode.com/problems/balanced-binary-tree

点击查看代码
public class BalancedBinaryTree {
    /**
     * balancedFlag: true / false
     * 最终返回二叉树是否平衡,递归进行过程中携带其子树是否平衡
     * height:
     * 最终返回二叉树的高度,递归进行过程中携带每一棵子树的高度
     */
    public static class Info {
        public boolean balancedFlag;
        public int height;

        public Info(boolean balancedFlag, int height) {
            this.balancedFlag = balancedFlag;
            this.height = height;
        }
    }

    public static boolean isBalanced(TreeNode root) {
        return process(root).balancedFlag;
    }

    /**
     * 递归判断二叉树是否是平衡二叉搜索树
     *
     * @param root 二叉树根节点
     * @return Info
     */
    private static Info process(TreeNode root) {
        if (root == null) {
            return new Info(true, 0);
        }
        Info leftInfo = process(root.left);
        Info rightInfo = process(root.right);
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        // 左树平衡,与右树平衡,与左右树深度差不超过 1
        boolean balancedFlag = leftInfo.balancedFlag && rightInfo.balancedFlag
                && Math.abs(leftInfo.height - rightInfo.height) < 2;
        return new Info(balancedFlag, height);
    }

    public static boolean isBalanced1(TreeNode root) {
        if (root == null) {
            return true;
        }
        // 非平衡树
        if (height(root) == -1) {
            return false;
        }
        return true;
    }

    public static int height(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftHeight = height(root.left);
        int rightHight = height(root.right);
        if (leftHeight == -1 || rightHight == -1) {
            return -1;
        }
        if (Math.abs(leftHeight - rightHight) > 1) {
            return -1;
        }
        return Math.max(leftHeight, rightHight) + 1;
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);
        root.left.left.left = new TreeNode(5);
        // root.left.right = new TreeNode(6);
        // root.right.left = new TreeNode(7);
        // 实现一
        // boolean balanced = isBalanced(root);
        // System.out.println(balanced);
        // 实现二
        int height = height(root);
        System.out.println(height);
    }
}

3.7、判断是否是二叉搜索树

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,
或者是具有下列性质的二叉树:

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树。
  • 设x是二叉搜索树中的一个结点。如果y是x左子树中的一个结点,那么y.key≤x.key。如果y是x右子树中的一个结点,那么y.key≥x.key。

3.7.2、代码实现

测试地址:https://leetcode.com/problems/validate-binary-search-tree/

点击查看代码
public class IsBinarySearchTree {

    public static class Info {
        // 是否是二叉搜索树
        public boolean BSTFlag;
        // 整棵树最小值
        public int min;
        // 整棵树最大值
        public int max;

        public Info(boolean BSTFlag, int min, int max) {
            this.BSTFlag = BSTFlag;
            this.min = min;
            this.max = max;
        }
    }

    public boolean isValidBST(TreeNode root) {
        return process(root).BSTFlag;
    }

    public static Info process(TreeNode x) {
        if (x == null) {
            return null;
        }
        Info leftInfo = process(x.left);
        Info rightInfo = process(x.right);
        // 假设当前节点就是最大值、最小值
        int min = x.val;
        int max = x.val;
        if (leftInfo != null) {
            min = Math.min(leftInfo.min, min);
            max = Math.max(leftInfo.max, max);
        }
        if (rightInfo != null) {
            min = Math.min(rightInfo.min, min);
            max = Math.max(rightInfo.max, max);
        }

        boolean BSTFlag = true;
        if (leftInfo != null && !leftInfo.BSTFlag) {
            BSTFlag = false;
        }
        if (rightInfo != null && !rightInfo.BSTFlag) {
            BSTFlag = false;
        }

        boolean leftMaxLessX = leftInfo == null ? true : (leftInfo.max < x.val);
        boolean rightMinMoreX = rightInfo == null ? true : (rightInfo.min > x.val);
        if (!leftMaxLessX || !rightMinMoreX) {
            BSTFlag = false;
        }

        return new Info(BSTFlag, min, max);
    }
}

3.8、能否组成路径和

给定一棵二叉树的根和一个整数 targetSum,如果树有一条从根到叶的路径,
使得沿路径的所有值相加等于 targetSum,则返回 true。 叶子是没有孩子的节点。

3.8.2、代码实现

方式一:从根节点累加到叶子节点,判断和是否等于targetSum
方式二:用targetSum 减去其所经过节点的值,到叶子节点,判断剩余值是否与叶子节点的值相等
测试链接:https://leetcode.com/problems/path-sum

点击查看代码
public class PathSum {

    public static boolean isSum = false;

    /**
     * 能否组成路径和(实现一)
     *
     * @param root      根节点
     * @param targetSum 目标和
     * @return true / false,是否有满足条件的路径
     */
    public static boolean hasPathSum(TreeNode root, int targetSum) {
        if (root == null) {
            return false;
        }
        isSum = false;
        process(root, 0, targetSum);
        return isSum;
    }

    /**
     * @param cur       当前节点
     * @param preSum    当前节点之前的,所有节点的和
     * @param targetSum 目标和
     */
    private static void process(TreeNode cur, int preSum, int targetSum) {
        if (cur.left == null && cur.right == null) {
            if (preSum + cur.val == targetSum) {
                isSum = true;
            }
            return;
        }
        // else cur 为非叶子节点
        // 加上当前节点的值往下传
        preSum += cur.val;
        if (cur.left != null) {
            process(cur.left, preSum, targetSum);
        }
        if (cur.right != null) {
            process(cur.right, preSum, targetSum);
        }
    }

    /**
     * 能否组成路径和(实现二)
     *
     * @param root      根节点
     * @param targetSum 目标和
     * @return true / false,是否有满足条件的路径
     */
    public boolean hasPathSum1(TreeNode root, int targetSum) {
        if (root == null) {
            return false;
        }
        return process1(root, targetSum);
    }

    /**
     * 用 targetSum 减去其所经过节点的值,到叶子节点时,如果剩余数与之相等,则找到了
     * @param cur 当前节点
     * @param res 经过节点后的剩余数
     * @return true / false,是否有满足条件的路径
     */
    private boolean process1(TreeNode cur, int res) {
        if (cur.left == null && cur.right == null) {
            return cur.val == res;
        }
        boolean ans = cur.left != null ? process1(cur.left, res - cur.val) : false;
        ans |= cur.right != null ? process1(cur.right, res - cur.val) : false;
        return ans;
    }

3.9、收集达标路径和

给定二叉树的根和整数 targetSum,返回所有根到叶的路径,其中路径中节点值的总和等于 targetSum。
每条路径都应作为节点值列表返回,而不是节点引用。

  • 根到叶路径是从根开始到任何叶节点结束的路径。叶子是没有孩子的节点。

3.9.1、代码实现

点击查看代码
public class PathSumII {

    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<List<Integer>> ans = new ArrayList<>();
        if (root == null) {
            return ans;
        }
        List<Integer> path = new ArrayList<>();
        process(root, path, 0, targetSum, ans);
        return ans;
    }

    /**
     * 收集达标路径和
     *
     * @param cur       当前节点
     * @param path      到达当前节点已经过的父节点集合
     * @param preSum    到达当前节点已经过的父节点累加和
     * @param targetSum 目标和
     * @param ans       满足条件的路径集合
     */
    private void process(TreeNode cur, List<Integer> path, int preSum, int targetSum, List<List<Integer>> ans) {
        if (cur.left == null && cur.right == null) {
            if (preSum + cur.val == targetSum) {
                path.add(cur.val);
                // copy 一份放入ans
                ans.add(copy(path));
                // 返回其父节点时,将自己移除
                path.remove(path.size() - 1);
            }
            return;
        }
        // else 非叶子节点
        path.add(cur.val);
        preSum += cur.val;
        if (cur.left != null) {
            process(cur.left, path, preSum, targetSum, ans);
        }
        if (cur.right != null) {
            process(cur.right, path, preSum, targetSum, ans);
        }
        // 返回其父节点时,将自己移除
        path.remove(path.size() - 1);
    }

    public List<Integer> copy(List<Integer> path) {
        List<Integer> res = new ArrayList<>();
        res.addAll(path);
        return res;
    }
}

标签:return,int,节点,二叉树,root,public,left
From: https://www.cnblogs.com/kaibo-li/p/16927145.html

相关文章