java数据结构--二叉树

本文详细介绍了二叉树的概念、节点类型,以及前序、中序、后序遍历方法。还探讨了递归和非递归方式求解二叉树深度,包括最大深度和最小深度。此外,文章还涉及对称二叉树、翻转二叉树、后缀表达式构造树和根据前后序遍历来还原树的过程。
摘要由CSDN通过智能技术生成

目录

一.概念

 二.构建二叉树节点类TreeNode

 三.二叉树的遍历

 1.前序遍历preOrder

2.中序遍历medOrder

3.后序遍历postOrder

 4.非递归遍历

三.深度

1.概念

2.递归求最大深度

 3.层序遍历加队列求最大深度

4.测试

5.递归求最小深度

6.层序遍历加队列求最小深度

7.测试

四.对称二叉树

 五.翻转二叉树

六.后缀表达式构造树

七.根据前序遍历和中序遍历还原树

八.根据后序和中序遍历还原树


一.概念

二叉树是一种树状结构,其中每个节点最多有两个子节点,被称为左子节点右子节点。二叉树通常具有以下特点:

  1. 根节点:二叉树的最顶层节点被称为根节点。它没有父节点,是整个树的起点。

  2. 子节点:每个节点最多有两个子节点,分别称为左子节点和右子节点。左子节点在树结构中位于父节点的左侧,右子节点在右侧。

  3. 叶节点:没有子节点的节点被称为叶节点,也被称为终端节点。叶节点位于树的最底层。

  4. 父节点:每个节点的上一层节点被称为父节点。每个节点除了根节点都有一个父节点。

  5. 兄弟节点:拥有相同父节点的节点被称为兄弟节点。

  6. 深度:节点的深度是指从根节点到该节点的路径上的节点数。

  7. 高度:节点的高度是指从该节点到树的最底层叶节点的最长路径。

  8. 子树:节点及其子节点以及与之相关的边所构成的树称为子树。

二叉树在计算机科学中具有广泛的应用,例如在搜索树排序算法中的使用。因为二叉树具有简单的结构和快速的搜索能力,所以它被广泛应用于许多领域

 二.构建二叉树节点类TreeNode

package 树.二叉树;

/**
 * 普通二叉树
 */
public class TreeNode {

     int value;

     TreeNode left;

     TreeNode right;


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

    @Override
    public String toString() {
        return "TreeNode{" +
                "value=" + value +
                ", left=" + left +
                ", right=" + right +
                '}';
    }
}

 三.二叉树的遍历

 1.前序遍历preOrder

    /**
     * 前序遍历
     *
     * @param treeNode
     */
    static void preOrder(TreeNode treeNode) {
        if (treeNode == null) {
            return;
        }
        System.out.print(treeNode.value + "\t");
        preOrder(treeNode.left);    //左
        preOrder(treeNode.right);   //右
    }

2.中序遍历medOrder

    /**
     * 中序遍历
     *
     * @param treeNode
     */
    static void medOrder(TreeNode treeNode) {
        if (treeNode == null) {
            return;
        }
        medOrder(treeNode.left);    //左
        System.out.print(treeNode.value + "\t");
        medOrder(treeNode.right);   //右
    }

3.后序遍历postOrder

    /**
     * 后序遍历
     *
     * @param treeNode
     */
    static void postOrder(TreeNode treeNode) {
        if (treeNode == null) {
            return;
        }
        postOrder(treeNode.left);    //左
        postOrder(treeNode.right);   //右
        System.out.print(treeNode.value + "\t");
    }

分别打印一下:

 

 public static void main(String[] args) {

        /**
         *            1
         *           / \
         *          2   3
         *         /   / \
         *        6   9   8
         */


        TreeNode root = new TreeNode(
                1,
                new TreeNode(2, new TreeNode(6, null, null), null),
                new TreeNode(3, new TreeNode(9, null, null), new TreeNode(8, null, null))
        );
        System.out.println("前序遍历");
        preOrder(root);
        System.out.println();
        System.out.println("中序遍历");
        medOrder(root);
        System.out.println();
        System.out.println("后序遍历");
        postOrder(root);

    }

运行:

前序遍历
1	2	6	3	9	8	
中序遍历
6	2	1	9	3	8	
后序遍历
6	2	9	8	3	1	
进程已结束,退出代码0

 4.非递归遍历

 我们可以使用栈(stack)来实现二叉树的遍历,栈可以模拟递归

 /**
     * 通过迭代实现三种遍历
     */
    @Test
    public void testAllOrder(){
        /**
         *            1
         *           / \
         *          2   3
         *         /   / \
         *        6   9   8
         */
        TreeNode root = new TreeNode(
                1,
                new TreeNode(2, new TreeNode(6, null, null), null),
                new TreeNode(3, new TreeNode(9, null, null), new TreeNode(8, null, null))
        );

        //创建栈
        LinkedListStack<TreeNode> stack = new LinkedListStack<>(10);

        //定义指针变量,初始指向root节点
        TreeNode node = root;
        TreeNode pop = null;
        //遍历,只要node不为空,并且栈中有元素
        while (node != null || !stack.isEmpty()) {
            //先找左孩子
            if (node != null) {
                //压入栈
                stack.push(node);
                System.out.println("前序:"+node.value);
                //指向左孩子
                node = node.left;
            } else {
                //弹出元素
                TreeNode peek = stack.peek();
                // 没有右子树
                if (peek.right == null ) {
                    System.out.println("中序遍历:"+peek.value);
                    //弹出
                    pop = stack.pop();
                    System.err.println("后序:"+pop.value);
                }
                //右子树处理完成
                else if ( peek.right == pop) {
                    pop = stack.pop();
                    System.err.println("后序:"+pop.value);
                }
                //待处理右子树
                else {
                    System.out.println("中序遍历:"+peek.value);
                    //指向栈顶元素的右孩子
                    node = peek.right;
                }

            }

        }

    }

测试一下:

前序:1
前序:2
前序:6
后序:6
中序遍历:6
后序:2
中序遍历:2
后序:9
中序遍历:1
后序:8
前序:3
前序:9
中序遍历:9
后序:3
中序遍历:3
后序:1
前序:8
中序遍历:8

三.深度

1.概念

  二叉树的深度是指从根节点最远叶子节点的路径上的节点个数。可以通过递归迭代的方式来计算二叉树的深度。

递归方法:

  1. 如果二叉树为空,返回深度为 0。
  2. 否则,分别计算左子树和右子树的深度。
  3. 树的深度为左子树深度和右子树深度中的较大值加 1。

迭代方法(层次遍历):

  1. 如果根节点为空,返回深度为 0。
  2. 创建一个队列,并将根节点入队。
  3. 初始化深度为 0。
  4. 循环执行以下步骤直到队列为空:
    • 获取当前层的节点个数,记为 count。
    • 将当前层的节点依次出队,并将它们的左子节点和右子节点依次入队。
    • 将 count 减去当前层节点个数,如果 count 大于 0,则深度加 1。
  5. 返回深度的值。

无论使用递归还是迭代的方式,都可以得到二叉树的深度。

2.递归求最大深度


    /**
     * 递归调研求最大深度
     * @param node
     * @return
     */
    static int maxDepth(TreeNode node){

        if(node == null){
            return 0;
        }
        if(node.left == null && node.right == null){
            return 1;
        }

        int d1 = maxDepth(node.left);
        int d2 = maxDepth(node.right);
        return Integer.max(d1,d2) + 1;


    }

 3.层序遍历加队列求最大深度

/**
     *  通过层序遍历 加 队列 来实现求最大深度
     *  思路:
     *     每次将一层的节点加入到队列中,然后循环,到下一层取出队列中的元素,如果该
     *     元素右左右子树,那么继续加入到队列中去,
     * @param root
     * @return
     */
    static  int maxDepthByQueue(TreeNode root){
        if(root==null){
            return 0;
        }
        //创建队列
        Queue<TreeNode> queue = new LinkedList<>();
        //先把根节点加入到队列中去
        queue.offer(root);
        //初始深度为0
        int depth = 0;
        //当队列不为空时循环
        while (!queue.isEmpty()){
            //相当于每一层的个数
            for (int i =0;i < queue.size(); i++){
                //从队头取出元素
                TreeNode poll = queue.poll();
                //如果有左孩子,把左孩子加入到队列中
                if(poll.left != null){
                    queue.offer(poll.left);
                }
                //如果有右孩子,把右孩子加入到队列中
                if(poll.right != null){
                    queue.offer(poll.right);
                }
            }
            //每遍历完一层,让深度加一
            depth++;
        }
        return depth;
    }

4.测试

    /**
     *          1
     *         / \
     *        2   3
     *      /      \
     *     4        5
     *               \
     *                6
     *
     *    左子树最大深度为3,右子树为4,所以最大深度为4
     */
    public static void main(String[] args) {
        TreeNode root = new TreeNode(1,new TreeNode(2,new TreeNode(4,null,null),null),
                new TreeNode(3,null,new TreeNode(5,null,new TreeNode(6,null,null))));
        System.out.println("递归法:");
        System.out.println(maxDepth(root));
        System.out.println("层序队列法:");
        System.out.println(maxDepthByQueue(root));
    }

 运行:

递归法:
4
层序队列法:
4

进程已结束,退出代码0

5.递归求最小深度

    /**
     * 递归求最小深度
     * @param root
     * @return
     */
   static int minDepth(TreeNode root){

        if(root == null){
            return 0;
        }
        int d1 = minDepth(root.left);
        int d2 = minDepth(root.right);

        //为了防止单边树,如果一边为null,那么应该返回另一边的深度
        if(d1 == 0){
            return d2 + 1;
        }
        if(d2 == 0){
            return d1 + 1;
        }

        return Integer.min(d1,d2) + 1;
   }

6.层序遍历加队列求最小深度

 /**
     * 层序遍历找最小深度,
     *  思路:
     *    如果找到第一个叶子节点,那么该叶子节点所在的层就是最小层
     * @param root
     * @return
     */
   static int minDepthFloorByQueue(TreeNode root){
       if(root==null){
           return 0;
       }
       LinkedList<TreeNode> queue = new LinkedList<>();
       int depth = 0;
       queue.offer(root);
       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.right != null){
                   queue.offer(poll.right);
               }
           }
       }
       return depth;
   }
}

7.测试

 

 public static void main(String[] args) {

        /**
         *          1
         *         / \
         *        2   3
         *      /      \
         *     4        5
         *               \
         *                6
         *
         *    左子树最大深度为3,右子树为4,所以最大深度为4
         */

            TreeNode root = new TreeNode(1,new TreeNode(2,new TreeNode(4,null,null),null),
                    new TreeNode(3,null,new TreeNode(5,null,new TreeNode(6,null,null))));
            System.out.println("递归法:");
            System.out.println(minDepth(root));
            System.out.println("层序队列法:");
            System.out.println(minDepthFloorByQueue(root));


    }

运行:

递归法:
3
层序队列法:
3

进程已结束,退出代码0

四.对称二叉树

/**
 * 对称二叉树
 */
public class D_Eq_Tree {
    /**
     *   左的左和右的右相等,
     *   左的右和右的左相等
     *
     * @param args
     */

    public static void main(String[] args) {

        /**
         *           1
         *          / \
         *         2   2
         *        /\   /\
         *       3  4 4  3
         */
     TreeNode root =   new TreeNode(1,new TreeNode(2,new TreeNode(3,null,null),new TreeNode(4,null,null)),
                new TreeNode(2,new TreeNode(4,null,null),new TreeNode(3,null,null)));


        boolean flag = isD_EqTree(root.left, root.right);
        System.out.println(flag);

    }

     static boolean isD_EqTree(TreeNode left,TreeNode right){
        //如果左子树和右子树都为null,则对称
         if(left==null && right==null){
             return true;
         }
         //如果左子树或者右子树为Null,则不对称
         if(left==null || right==null){
             return false;
         }
         //如果左右两边值不相等,则不对称
         if(left.value != right.value){
             return false;
         }
         //左右递归
         return isD_EqTree(left.left,right.right) && isD_EqTree(left.right,right.left);



     }


}

测试运行:

true

进程已结束,退出代码0

 五.翻转二叉树

/**
 * 翻转二叉树
 */
public class L226_ReversedTree {


    public static void main(String[] args) {
        /**
         *         1                       1
         *        / \                     / \
         *       2   3        =>         3   2
         *      / \ / \                 / \ / \
         *     4  5 6  7               7  6 5 4
         */
        TreeNode root = new TreeNode(1,
                new TreeNode(2,new TreeNode(4,null,null),new TreeNode(5,null,null)),
                new TreeNode(3,new TreeNode(6,null,null),new TreeNode(7,null,null)));

               preOrder(root);


        System.out.println();
        reversed(root);

        preOrder(root);

    }

    /**
     * 翻转二叉树
     * @param root
     */
    static void reversed(TreeNode root){
             if(root==null){
                 return;
             }
             TreeNode node = root.left;
             root.left = root.right;
             root.right = node;
             //递归左子树
            reversed(root.left);
            //递归右子树
            reversed(root.right);

       }

        static void preOrder(TreeNode root){
            if(root==null){
                return;
            }
            System.out.print(root.value+"\t");
            preOrder(root.left);
            preOrder(root.right);
        }
        
}

运行:

1	2	4	5	3	6	7	
1	3	7	6	2	5	4	
进程已结束,退出代码0

六.后缀表达式构造树

/**
 * 后缀表达式构造树
 */
public class Last_Expression {

    /**
     *     中缀: (2-1)*3
     *     后缀:  21-3*
     *
     *     树:
     *              *
     *             / \
     *            -   3
     *           / \
     *          2   1
     *
     *    后序遍历就能获得 : 21-3*
     *
     *
     * @param args
     */


    public static void main(String[] args) {

        String[] str = {"2","1","-","3","*"};
        LinkedList<TreeStrNode> stack = new LinkedList<>();

        for (String c : str) {

            switch (c){
                case "+":
                case "-":
                case "*":
                case "/":
                    //先弹出的为右孩子
                    TreeStrNode right = stack.pop();
                    //后弹出的为左孩子
                    TreeStrNode left = stack.pop();
                    //创建树
                    TreeStrNode parent = new TreeStrNode(c);
                    parent.left = left;
                    parent.right = right;
                    //最后把父节点压入栈
                    stack.push(parent);
                    break;
                default:
                    //如果不是运算符,就压入栈
                    stack.push(new TreeStrNode(c));
            }
        }
        //最终栈中的节点为树的根节点
        TreeStrNode root = stack.peek();

        //对他做一个后序遍历
        postOrder(root);

    }

    /**
     * 后序遍历
     *
     * @param treeNode
     */
    static void postOrder(TreeStrNode treeNode) {
        if (treeNode == null) {
            return;
        }
        postOrder(treeNode.left);    //左
        postOrder(treeNode.right);   //右
        System.out.print(treeNode.str + "\t");
    }


}

 运行:

2	1	-	3	*	
进程已结束,退出代码0

七.根据前序遍历和中序遍历还原树

/**
 * 根据前序遍历和中序遍历 还原树
 */
public class Pre_In_ToTree {
    /**
     *            1
     *           / \
     *          2   3
     *         /   / \
     *        6   9   8
     *
     *        preOrder = 1, 2, 6, 3, 9, 8
     *        inOrder = 6, 2, 1, 9 , 3, 8
     *
     *  思路:
     *     前序遍历的第一个是根节点 root = 1
     *     然后找到中序遍历中 root所在的位置
     *     ->左边的是左子树
     *     ->右边的是右子树
     *     中序:
     *      左: 6,2
     *      右: 9,3,8
     *     前序:
     *      左: 2,6
     *      右: 3,9,8
     *
     *     这样根据中序的左和前序的左,可以知道 6 是左子树, 2 是父节点
     *         根据中序的右和前序的右,可以知道 9是左子树,8是右子树,3是父节点
     *
     *     最后递归调用继续分割左右数组
     *
     *
     */

    public static void main(String[] args) {

      int[] preOrder = {1, 2, 6, 3, 9, 8};
      int[]  inOrder = {6, 2, 1, 9 , 3, 8};

        TreeNode root = getTree(preOrder, inOrder);

        preOrder(root);

    }


    static TreeNode getTree(int[] preOrder,int[] inOrder){

        //结束递归条件
        if(preOrder.length == 0){
            return null;
        }

        //先找到根节点
        int rootValue = preOrder[0];
        TreeNode root = new TreeNode(rootValue, null, null);
        //在中序中找到根节点的位置
        for (int i = 0; i < inOrder.length ; i++) {
            if(inOrder[i] == rootValue){
                //找到之后切割左子树和右子树
                //inOrder.left = 0 ~ i-1
                //inOrder.right = i+1 ~ inOrder.length-1;
                int[] inLeft = Arrays.copyOfRange(inOrder, 0, i);
                int[] inRight = Arrays.copyOfRange(inOrder, i + 1, inOrder.length);
                //继续切割preOrder
                //preOrder.left = 1 ~ i+1
                //preOrder.right = i+2 ~ preOrder.length-1
                int[] preLeft = Arrays.copyOfRange(preOrder, 1, i+1);
                int[] preRight = Arrays.copyOfRange(preOrder, i + 1, preOrder.length);

                //递归调用
               root.left = getTree(preLeft,inLeft);
               root.right = getTree(preRight,inRight);
              break;
            }
        }


           return root;
    }


    /**
     * 前序遍历
     *
     * @param treeNode
     */
    static void preOrder(TreeNode treeNode) {
        if (treeNode == null) {
            return;
        }
        System.out.print(treeNode.value + "\t");
        preOrder(treeNode.left);    //左
        preOrder(treeNode.right);   //右
    }

}

运行:

1	2	6	3	9	8	
进程已结束,退出代码0

 

八.根据后序和中序遍历还原树

/**
 * 根据后序和中序遍历还原树
 */
public class Post_In_ToTree {



/**
 *            1
 *           / \
 *          2   3
 *         /   / \
 *        6   9   8
 *
 *        postOrder = 6,2,9,8,3,1
 *        inOrder = 6, 2, 1, 9 , 3, 8
 *
 **/

public static void main(String[] args) {

    int[] postOrder = {6,2,9,8,3,1};
    int[]  inOrder = {6, 2, 1, 9 , 3, 8};

    TreeNode root = buildTree(postOrder, inOrder);

    preOrder(root);


}


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

    //先找根节点
    int rootValue = postOrder[postOrder.length - 1];
    TreeNode root = new TreeNode(rootValue, null, null);

    for (int i = 0; i < inOrder.length; i++) {
        if(inOrder[i] == rootValue){
            //切分
            int[] inLeft = Arrays.copyOfRange(inOrder, 0, i);
            int[] inRight = Arrays.copyOfRange(inOrder, i + 1, inOrder.length);
            int[] postLeft = Arrays.copyOfRange(postOrder, 0, i);
            int[] postRight = Arrays.copyOfRange(postOrder, i , postOrder.length - 1);

            //递归
            root.left = buildTree(postLeft,inLeft);
            root.right = buildTree(postRight,inRight);
            break;
        }

    }

      return root;

}


}

运行:

1	2	6	3	9	8	
进程已结束,退出代码0

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值