JAVA 实现 - 二叉树(一)

  1. 广度优先遍历(Breadth-first order): 尽可能先访问距离根节点最近的节点,也称为层序遍历
  2. 深度优先遍历(Depth-first order): 对于二叉树,可以进一步分成三种:
    • pre-order 前序遍历,对于每一颗子树,先访问该节点,然后是左子树,最后是右子树
    • in-order 中序遍历,对于每一棵子树,先访问左子树,然后是该节点,最后是右子树
    • post-order 后序遍历,对于一颗子树,先访问左子树,然后是右子树,最后是该节点

前中后遍历 - 递归实现

树的节点:

package com.datastructure.binarytree;

public class TreeNode {

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

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

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

    @Override
    public String toString() {
        return String.valueOf(this.val);
    }
}

实现:

package com.datastructure.binarytree;

public class TreeTraversal {

    /*
        1
       / \
      2   3
     /   /\
    4   5  6
     */
    public static void main(String[] args) {
        // 构建一颗树
        TreeNode node = new TreeNode(new TreeNode(new TreeNode(4), 2,null), 1,
                new TreeNode(new TreeNode(5), 3, new TreeNode(6)));

        System.out.println("前序遍历:");
        preOrder(node);
        System.out.println();


        System.out.println("中序遍历");
        inOrder(node);
        System.out.println();

        System.out.println("后序遍历");
        postOrder(node);
        System.out.println();
    }

    /**
     * 递归实现前序遍历:
     *  遍历规则:先节点值,在左节点,在右节点
     *  输出结果:123456
     */
    static void preOrder(TreeNode node){
        if (node == null){
            return ;
        }
        System.out.print(node.val +"\t"); //节点值
        preOrder(node.left); //左
        preOrder(node.right); //左

    }

    /**
     * 递归实现中序遍历:
     * 遍历规则:先左节点,再节点值,最后右节点
     * 输出结果:4	2	1	5	3	6
     */
    static void inOrder(TreeNode node){

        if (node == null){
            return;
        }
        inOrder(node.left);
        System.out.print(node.val + "\t");
        inOrder(node.right);
    }

    /**
     * 递归实现后序遍历
     *  遍历规则:先左节点,再右节点,最后节点值
     *  输出结果:4	2	5	6	3	1
     */
    static void postOrder(TreeNode node){

        if (node == null){
            return;
        }
        postOrder(node.left);
        postOrder(node.right);
        System.out.print(node.val + "\t");

    }
}

/*
输出:

前序遍历:
1	2	4	3	5	6	
中序遍历
4	2	1	5	3	6	
后序遍历
4	2	5	6	3	1	
*/

前中遍历 - 非递归实现

package com.datastructure.binarytree;

import java.util.LinkedList;

public class TreeTraversal2 {

    /*
        1
       / \
      2   3
     /   /\
    4   5  6

    遍历二叉树 - 非递归实现
     */
    public static void main(String[] args) {
        // 构建一颗树
        TreeNode root = new TreeNode(new TreeNode(new TreeNode(4), 2,null), 1,
                new TreeNode(new TreeNode(5), 3, new TreeNode(6)));


        LinkedList<TreeNode> stack = new LinkedList<>(); //模拟栈

        TreeNode curr = root;
        while(curr != null || !stack.isEmpty()){   //当前节点为null且栈为空时才结束循环
            if(curr != null){
                colorPrintln("去 " + curr.val,31);
                stack.push(curr); //压入栈,记录去的路径
                curr = curr.left;  //更新当前节点
            }else{   //curr 为null, 开始往回走
                TreeNode pop = stack.pop();   //第一次弹出的栈顶为左子树的最后一个节点
                colorPrintln("回:" + pop.val,34);
                curr = pop.right;  // 当弹出最后一个为根节点时,更新 curr 为根节点的右节点
                                    // 其他情况弹出的元素的右节点还是 null
            }
        }
    }

    /*
    提供的一个额外打印的方法,方便观察输出的结果
    */
    private  static void colorPrintln(String origin, int color){  
        System.out.printf("\033[%dm%s\033[0m%n", color, origin);
    }

}

其实结果就是:
前序遍历:1,2,4,3,5,6
中序遍历:4,2,1,5,3,6

后续遍历 - 非递归实现

package com.datastructure.binarytree;

import java.util.LinkedList;

public class TreeTraversal3 {

    /*
        1
       / \
      2   3
     / \   /\
    4   7  5  6

    遍历二叉树 - 非递归实现
     */
    public static void main(String[] args) {
        // 构建一颗树
        TreeNode root = new TreeNode(new TreeNode(new TreeNode(4), 2,new TreeNode(7)), 1,
                new TreeNode(new TreeNode(5), 3, new TreeNode(6)));


        LinkedList<TreeNode> stack = new LinkedList<>(); //模拟栈

        TreeNode curr = root;
        TreeNode pop = null; //记录上次弹栈的元素
        while (curr != null || !stack.isEmpty()){
            if (curr!= null){
                stack.push(curr);
                curr = curr.left;  //左子树走到头
            }else{    //开始往会走

                //获取栈顶元素
                TreeNode peek = stack.peek();
                if(peek.right == null || peek.right == pop){  //栈顶元素的右子树为空或者上次一弹栈的元素等于栈顶元素的右子树才能弹栈
                    //弹栈取出栈顶节点
                    pop = stack.pop();
                    System.out.println("回:" + pop.val);
                }else{    //如果栈顶元素的右子树不为null,处理栈顶元素的右子树
                    curr = peek.right;
                }

            }

        }
    }
}

/*
输出:
回:4
回:7
回:2
回:5
回:6
回:3
回:1
*/

前中后序遍历一起非递归实现

package com.datastructure.binarytree;

import java.util.LinkedList;

public class TreeTraversal4 {

    /*
        1
       / \
      2   3
     / \   /\
    4   7  5  6

    遍历二叉树 - 非递归实现
     */
    public static void main(String[] args) {
        // 构建一颗树
        TreeNode root = new TreeNode(new TreeNode(new TreeNode(4), 2,new TreeNode(7)), 1,
                new TreeNode(new TreeNode(5), 3, new TreeNode(6)));


        LinkedList<TreeNode> stack = new LinkedList<>(); //模拟栈

        TreeNode curr = root;
        TreeNode pop = null;//记录上次弹栈的元素
        while (curr != null || !stack.isEmpty()){
            if (curr != null){  //处理左子树
                stack.push(curr);
                colorPrintln("前序遍历: " + curr.val, 31);
                curr = curr.left;
            }else{
                TreeNode peek = stack.peek();

                if(peek.right == null){ // 没有右子树
                    colorPrintln("中序遍历: " + peek.val, 36 );
                    pop = stack.pop();
                    colorPrintln("后序遍历: " + pop.val, 34);

                }else if(peek.right == pop){  //右子树处理完成
                    pop = stack.pop();
                    colorPrintln("后序遍历: " + pop.val, 34 );
                }
                else{ // 待处理右子树
                    colorPrintln("中序遍历: " + peek.val, 36 );
                    curr = peek.right;
                }

            }

        }
    }
    private  static void colorPrintln(String origin, int color){
        System.out.printf("\033[%dm%s\033[0m%n", color, origin);
    }

}

判断一个二叉树是否为对称二叉树 - 递归实现

对称二叉树是一种特殊的二叉树,它的左子树和右子树在结构上是镜像对称的,即它们的形式和节点的值都是对称的。更严格的定义是:如果一个二叉树的每个节点都右左右两个子节点,并且左子节点的值等于右子节点的值,那么这个二叉树就是对称的。

如下一个对称二叉树:

package com.datastructure.binarytree;

public class E04Leetcode101 {

    public static void main(String[] args) {
        TreeNode root = new TreeNode(new TreeNode(new TreeNode(3),2,new TreeNode(4)),
                1,
                new TreeNode(new TreeNode(4),2,new TreeNode(3)));
        System.out.println(isSymmetric(root));
    }

    public static boolean isSymmetric(TreeNode root){
        return check(root.left, root.right);
    }

    private static boolean check(TreeNode left, TreeNode right) {
        if(left == null && right == null){
            return true;
        }
        //左子树和右子树不同时为空
        if(left == null || right == null){
            return  false;
        }

        if(left.val != right.val){
            return false;
        }
        return check(left.left,right.right) && check(left.right, right.left); //递归比较孩子
    }
}

二叉树的最大深度 - 后序遍历求解

/**
 * 求二叉树的最大深度 - 使用后续遍历(非递归)求解
 */
public class E05Leetcode104_2 {
    
        /*
                1
               / \
              2   3
             / \   /\
            4   7  5  6
           /
          8
          最大深度为: 4
     */
    public static void main(String[] args) {
        TreeNode root = new TreeNode(new TreeNode(new TreeNode(new TreeNode(8),4,null), 2,new TreeNode(7)), 1,
                new TreeNode(new TreeNode(5), 3, new TreeNode(6)));
        int i = maxDepth(root);
        System.out.println(i);  //out: 4
    }

    public static int maxDepth(TreeNode root){
        TreeNode curr = root;
        TreeNode pop = null; //记录最近一次的弹栈元素
        int max = 0; //记录树的最大高度
        LinkedList<TreeNode> stack = new LinkedList<>();

        while(curr != null || !stack.isEmpty()) {
            if (curr != null) { //向左走
                stack.push(curr);
                curr = curr.left;
                int size = stack.size();
                if (size > max) {
                    max = size;
                }
            } else { //往回走
                //待处理右子树
                TreeNode peek = stack.peek();
                if (peek.right == null || peek.right == pop) {
                    pop = stack.pop();
                } else {
                    curr = peek.right;
                }
            }
        }
        return max;
    }
}

二叉树的最大深度 - 层序遍历

思路:借助队列,将根节点放入队列尾部,取出头部元素,判断是否有子节点,有的话放入队列尾部,每遍历完一层数据(队列大小),深度加1

/**
 *
 * 二叉树的最大深度 - 层序遍历
 *   
 *             1
 *            / \
 *           2   3
 *          / \   /\
 *         4   7  5  6
 *        /
 *       8
 *       最大深度为: 4
 */
public class E05Leetcode104_3 {


    public static void main(String[] args) {
        TreeNode root = new TreeNode(new TreeNode(new TreeNode(new TreeNode(8),4,null), 2,new TreeNode(7)), 1,
                new TreeNode(new TreeNode(5), 3, new TreeNode(6)));
        System.out.println(maxDepth(root));
    }


    //使用层序遍历,层数即为最大深度
    public static int maxDepth(TreeNode node){
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(node); //插入队列尾部
        int depth  = 0;  //
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode poll = queue.poll(); //从队列头部取出节点
                System.out.print(poll.val + "\t");
                if (poll.left != null){
                    queue.offer(poll.left); //如果有左节点,插入队列
                }
                if (poll.right != null){
                    //插入队列
                    queue.offer(poll.right);
                }
            }
            System.out.println();  // 每一层遍历完毕输出换行
            depth ++ ;  // 深度加1
        }
        return depth;
    }
}

二叉树的最小深度 - 层序遍历

package com.datastructure.binarytree;
import java.util.LinkedList;
import java.util.Queue;


/**
 *
 * 二叉树的最小深度 - 层序遍历
 *
 *             1
 *            / \
 *           2   3
 *          / \   /\
 *         4   7  5  6
 *        /
 *       8
 *       最小深度为: 3
 */
public class E06Leetcode111_2 {

    public static void main(String[] args) {
        TreeNode root = new TreeNode(new TreeNode(new TreeNode(new TreeNode(8), 4, null), 2, new TreeNode(7)), 1,
                new TreeNode(new TreeNode(5), 3, new TreeNode(6)));
        System.out.println(minDepth(root));
    }


    public static int minDepth(TreeNode node) {
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(node);
        int depth = 0;
        while (!queue.isEmpty()) {  //不为空,进入下一层
            int size = queue.size();
            depth ++;  //相比寻找最大深度,自增操作提前。
            for (int i = 0; i < size; i++) {
                TreeNode poll = queue.poll();
                if (poll.left == null && poll.right == null) {  //找到第一个叶子节点
                    return depth;  // 第一个叶子节点所在的层次
                }
                if (poll.left != null) {
                    queue.offer(poll.left);
                }
                if (poll.left != null) {
                    queue.offer(poll.right);
                }
            }
        }
        return depth;
    }
}

翻转二叉树 - 递归实现

翻转规则:每个节点的左子树和右子树互换位置

package com.datastructure.binarytree;


/**
 * 反转二叉树
 *         1        反转后:     1
 *        / \                  / \
 *       2   3                3   2
 *      / \   /\             / \  /\
 *     4   7  5  6          6  5  7 4
 */
public class E07Leecode226 {

    public static void main(String[] args) {
        TreeNode root = new TreeNode(new TreeNode(new TreeNode(4), 2,new TreeNode(7)), 1,
                new TreeNode(new TreeNode(5), 3, new TreeNode(6)));
        System.out.println("反转之前:");
        E05Leetcode104_3.maxDepth(root);  //层序遍历
        TreeNode treeNode = invertTree(root);  
        System.out.println("反转之后:");
        E05Leetcode104_3.maxDepth(treeNode);; //层序遍历
    }
    public  static TreeNode invertTree(TreeNode root){
        fn(root);
        return root;
    }

    private  static void fn(TreeNode node){
        if(node == null){
            return;
        }
        
        TreeNode t  = node.left;
        node.left = node.right;
        node.right = t;
        fn(node.left);
        fn(node.right);
    }
}

输出:

反转之前:
1	
2	3	
4	7	5	6	
反转之后:
1	
3	2	
6	5	7	4	

后缀表达式转换为二叉树

package com.datastructure.binarytree;

import java.util.LinkedList;

public class E08ExpressionTree {

    static class TreeNode{
        public String val;
        public TreeNode left;
        public TreeNode right;

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

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

        @Override
        public String toString() {
            return this.val;
        }
    }

    /*
    中缀表达式:(2-1)*3
    后缀表达式(逆波兰)表达式: 21-3*

    思路:
        1.遇到数字入栈
        2.遇到运算符出栈,建立节点关系,再入栈

    栈:
    |   |
    | 3 |
    | - |
    ----
     表达式树
        *
       / \
      -   3
     / \
    2   1
     */

    public static TreeNode constructExpressionTree(String[] tokens){
        LinkedList<E08ExpressionTree.TreeNode> stack = new LinkedList<>();
        for (String t : tokens) {
            switch (t){
                case "+","-","*","/" -> {
                    TreeNode right  = stack.pop();
                    TreeNode left  = stack.pop();
                    TreeNode parent = new TreeNode(t);
                    parent.left = left;
                    parent.right = right;
                    stack.push(parent);
                }
                default -> {
                    stack.push(new TreeNode(t));
                }
            }
        }
        return stack.peek();
    }

    public static void main(String[] args) {
        String[] token = {"2","1","-","3","*"};
        // 构造树
        TreeNode root = constructExpressionTree(token);
        //后续遍历
        afterLoop(root);   //out: 2	1	-	3	*	
    }

 
}

根据前序和中序遍历结果构建二叉树 - 递归实现

package com.datastructure.binarytree;


import java.util.Arrays;

public class E09Leecode105 {

    /*
    根据前序和中序遍历结果构造二叉树
    preOrder = {1,2,4,3,6,7}
    inOrder  = {4,2,1,6,3,7}
    思路:
    1.根据前序遍历第一个元素:1 确定根节点
    2.在中序遍历中查找根节点1,
        中序:节点左边为左子树(4,2),节点右边为右子树(6,3,7)
        前序:左子树:(2,4),右子树(3,6,7)
    3.前序左子树2,4可以确定2为根节点,那么4为左子树,右节点为空
    4.前序3,6,7,可以确定3为根,结合中序:6为左,7为右
     */
    public static void main(String[] args) {

        int[] preOrder = {1, 2, 4, 3, 6, 7};
        int[] inOrder = {4, 2, 1, 6, 3, 7};

        TreeNode root = buildTree(preOrder, inOrder);

        TreeTraversal.preOrder(root);  //out: 1	2	4	3	6	7	
        System.out.println();
        TreeTraversal.inOrder(root);  //out: 4	2	1	6	3	7	
    }

    public static TreeNode buildTree(int[] preOrder, int[] inOrder) {
        if (preOrder.length == 0 || inOrder.length == 0) {
            return null;
        }

        int rootValue = preOrder[0];
        TreeNode root = new TreeNode(rootValue);
        for (int i = 0; i < inOrder.length; i++) {
            if (inOrder[i] == rootValue) {
                //0 ~ i-1,
                //i ~ inOrder.length-1
                int[] inLeftCopy = Arrays.copyOfRange(inOrder, 0, i - 1 + 1);//左开右闭所以加1    [4,2]
                int[] inRightCopy = Arrays.copyOfRange(inOrder, i+1, inOrder.length);//左开右闭所以加1  [6,3,7]

                //copy 前序遍历 左右子树
                int[] preLeftCopy = Arrays.copyOfRange(preOrder, 1, i + 1);  //[2,4]
                int[] preRightCopy = Arrays.copyOfRange(preOrder, i + 1, preOrder.length); //[3,6,7]

                TreeNode leftNode = buildTree(preLeftCopy, inLeftCopy);
                TreeNode rirhgtNode = buildTree(preRightCopy, inRightCopy);

                root.left = leftNode;
                root.right = rirhgtNode;
                break;
            }
        }
        return root;
    }
}

根据中序和后序遍历结果构建二叉树

package com.datastructure.binarytree;


import java.util.Arrays;

/**
 * 根据中序和后续遍历结果构造二叉树
 */
public class E10Leetcode106 {

    /*
        inOrder = {4,2,1,6,3,7}
        postOrder = {4,2,6,7,3,1}
    */

    public static void main(String[] args) {
        int[] inOrder = {4,2,1,6,3,7};
        int[] postOrder = {4,2,6,7,3,1};

        TreeNode treeNode = buildTree(inOrder, postOrder);

        TreeTraversal.inOrder(treeNode); //out: 4	2	1	6	3	7
        System.out.println();
        TreeTraversal.postOrder(treeNode);//out: 4	2	6	7	3	1

    }


    public static TreeNode buildTree(int[] inOrder, int[] postOrder) {
        if (inOrder.length == 0 || postOrder.length == 0) {
            return null;
        }
        //获得根节点的值
        int rootValue = postOrder[postOrder.length - 1];
        TreeNode root = new TreeNode(rootValue);
        for (int i = 0; i < inOrder.length; i++) {
            if (inOrder[i] == rootValue) {
                //截取中序 左右子树
                int[] inLeftCopy = Arrays.copyOfRange(inOrder, 0, i);
                int[] inRightCopy = Arrays.copyOfRange(inOrder, i+1, inOrder.length);

                //截取后序左右子树
                int[] postLeftCopy = Arrays.copyOfRange(postOrder, 0, i);
                int[] postRightCopy = Arrays.copyOfRange(postOrder, i, postOrder.length - 1);

                root.left = buildTree(inLeftCopy, postLeftCopy);
                root.right = buildTree(inRightCopy, postRightCopy);
            }
        }
        return root;
    }
}

求二叉搜索树的最近公共祖先

package com.datastructure.binarytree;


/**
 * 求二叉搜索树最近公共祖先(祖先也包括自己)
 * 前提:
 *  1.节点值唯一
 *  2.p 和 q 都存在
 */
public class ELeetcode235 {

    /*
            __ 6 __
           /      \
          2        8
         / \      / \
        0  4     7   9
          / \
         3   5
        
        思路: 待查找节点 p q在 某一节点的两侧,那么此节点就是最近的公共祖先
     */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        TreeNode curr = root;

        while(p.val < curr.val && q .val <  curr.val || p.val > curr.val && q.val > curr.val){ //条件成立,说明p q 在节点的同一侧
            if(p.val < curr.val){
                curr  = curr.left;
            }else {
                curr = curr.right;
            }
        }
        //退出循环时 当前节点在 p 和 q 的两侧
        return curr;
    }
}

posted @ 2023-12-17 20:53  chuangzhou  阅读(2)  评论(0编辑  收藏  举报