算法-二叉树问题

98 阅读4分钟

二叉树打印

题目: 构建一个二叉树,分别使用先、中、后序进行树的遍历打印

如图所示,是二叉树的基本结构。对于此树先、中、后序的遍历结果如图中所示的样子。

image-20240304230141735

image-20240304230230009

示例代码:

  • 树的节点结构

    public class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
    }
    
  • 构建插入节点元素操作

    /**
     * @param root 父节点
     * @param val  插入的值
     * @return 返回插入节点的父节点
     */
    private TreeNode insertRecursive(TreeNode root, int val) {
        if (root == null) {
            root = new TreeNode(val);
            return root;
        }
        if (val < root.val) {
            root.left = insertRecursive(root.left, val); // 插入到左节点
        }else{
            root.right=insertRecursive(root.right,val); // 插入到有节点
        }
        return root;
    }
    
  • 遍历打印

    /**
     * 遍历打印
     */
    public void inorderTraversal(){
        inorderTraversalRecursive(root);
    }
    ​
    private void inorderTraversalRecursive(TreeNode root) {
        if(root!=null){
            System.out.println(root.val); //先序遍历
            inorderTraversalRecursive(root.left);
            System.out.println(root.val); //中序遍历
            inorderTraversalRecursive(root.right);
            System.out.println(root.val); //后序遍历
        }
    }
    

可以看到在代码段 inorderTraversalRecursive 方法当中,只需要调整打印语句的位置就可以分别实现,先、中、后序的打印。

那么有趣的是,为什么只通过调整打印语句的顺序就可以实现,不同的遍历呢?其主要依赖的是递归调用的递归序。

二叉树的递归调用图示

image-20240304230927892

通过图中的顺序可以看到,在执行 inorderTraversalRecursive 函数方法的时候,每一个节点都没重复调用的 三次,而这三次的出现的时机,分别如图所示:

image-20240304231900614

关于树中某一节点元素左右交集的关系

题目:给出树中某一个元素 X ,关于 X 元素所在的树,分别进行先序与后序遍历,关于 X 节点之前的元素与之后的元素,两个去一个交集。而交集的元素代表的含义是什么?

先看图中的结果 【交集的节点有且全部都是该节点的 祖先节点】

image-20240304233059554

哪为什么交集一定就是祖先节点呢?

image-20240304235018567

  • 从图中可以看到,一棵树整体由三部分构成,分别是紫色的左子树,黄色右子树,以及红色色的根节点。
  • 先序遍历则表示,三种颜色出现的顺序为 红色->紫色->黄色
  • 后序遍历则表示,三种颜色的出现顺序为 紫色->黄色->红色
  • 因此对于 E 元素来说,其先序遍历黄色元素一定在右边而红色在左边,后序遍历黄色与红色都在右边。所以取交集,能取到的就只有黑色部分,也就是一定包含根节点。
  • 同样的原理套用在旁边的以 B 节点为根节点的子树上,一样适用,也就保证 E 元素的先后序遍历当中,左右两边分别各有根节点 B,也就保证了 B 节点一定取得到交集。
  • 这样就解答了上面 为什么交集一定就是祖先节点 的问题。

非递归遍历树

之前遍历树都是通过递归的方式,分别在递归序的不同时期打印输出,实现树的遍历。那么如果不同递归的方式可以实现树的遍历吗?

首先思考一下,递归的原理是什么?压栈! 既然是压栈那我们是否可以模拟递归的压栈方式,从而实现树的遍历呢?

用栈实现树的遍历

  • 先序遍历【利用栈先右后左的顺序进行压栈进行】

image-20240305001436164

  • 代码示例

    /**
     * 利用栈完成先序遍历
     * @param node 根节点
     */
    public static void preorderTraversal(TreeNode node){
        if(node!=null){
            Stack<TreeNode> stack=new Stack<TreeNode>();
            stack.push(node);
            while (!stack.empty()){
                node=stack.pop();
                System.out.println(node.val);
                if(node.right!=null){
                    stack.push(node.right);
                }
                if(node.left!=null){
                    stack.push(node.left);
                }
            }
        }
    }
    

代码附录

  • 树节点元素
package com.peppa.binaryTree;
​
/**
 * 二叉树的结构定义
 */
public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
​
    public TreeNode(int val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
​
    public int getVal() {
        return val;
    }
​
    public void setVal(int val) {
        this.val = val;
    }
​
    public TreeNode getLeft() {
        return left;
    }
​
    public void setLeft(TreeNode left) {
        this.left = left;
    }
​
    public TreeNode getRight() {
        return right;
    }
​
    public void setRight(TreeNode right) {
        this.right = right;
    }
​
    @Override
    public String toString() {
        return "TreeNode{" +
                "val=" + val +
                ", left=" + left +
                ", right=" + right +
                '}';
    }
}
  • 树结构与构建
package com.peppa.binaryTree;
​
/**
 * 二叉树类
 */
public class BinaryTree {
    TreeNode root;
​
    public BinaryTree() {
        this.root = null;
    }
​
    public void insert(int val) {
        root = insertRecursive(root, val);
    }
​
    /**
     * @param root 父节点
     * @param val  插入的值
     * @return 返回插入节点的父节点
     */
    private TreeNode insertRecursive(TreeNode root, int val) {
        if (root == null) {
            root = new TreeNode(val);
            return root;
        }
        if (val < root.val) {
            root.left = insertRecursive(root.left, val); // 插入到左节点
        }else{
            root.right=insertRecursive(root.right,val); // 插入到有节点
        }
        return root;
    }
​
    /**
     * 遍历打印
     */
    public void inorderTraversal(){
        inorderTraversalRecursive(root);
    }
​
    private void inorderTraversalRecursive(TreeNode root) {
        if(root!=null){
            System.out.println(root.val); //先序遍历
            inorderTraversalRecursive(root.left);
//            System.out.println(root.val); //中序遍历
            inorderTraversalRecursive(root.right);
//            System.out.println(root.val); //后序遍历
        }
    }
​
}
​
  • 压栈实现先序遍历
package com.peppa.binaryTree;
​
import java.util.Stack;
​
/**
 * 利用压栈的方式进行树的遍历
 */
public class PushBinaryTree {
​
    /**
     * 利用栈完成先序遍历
     * @param node 根节点
     */
    public static void preorderTraversal(TreeNode node){
        if(node!=null){
            Stack<TreeNode> stack=new Stack<TreeNode>();
            stack.push(node);
            while (!stack.empty()){
                node=stack.pop();
                System.out.println(node.val);
                if(node.right!=null){
                    stack.push(node.right);
                }
                if(node.left!=null){
                    stack.push(node.left);
                }
            }
        }
    }
}

算法文件地址