打印树形结构(可视化二叉树)

发布时间 2023-05-30 16:33:02作者: 旺仔古李

平时开发时,偶尔会操作二叉树,而查看二叉树的结构,是一种比较费时的事情,我们可以把它按照本身的结构打印出来,从而方便查看。

例如

        Node a = new Node(110);
        Node b = new Node(105);
        Node c = new Node(115);
        Node d = new Node(102);
        Node e = new Node(107);
        Node f = new Node(112);
        Node g = new Node(117);

        a.left = b;
        a.right = c;
        b.left = d;
        b.right = e;
        c.left = f;
        c.right = g;

当查看此树的数据结构,咱们一般会debug,一个一个节点的看,比较浪费时间。若是把它可视化的打印出来,一窥树的全貌,就能节省一些时间。

打印之后的树:

 代码如下:

import lombok.Data;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

/**
 * @author wzgl
 * @version 1.0
 * @date 2023/5/30 13:48
 */
public class PrintTree {

    /**
     * 打印二叉树
     *
     * @param info 树信息
     * @param <T>  树泛型
     * @return*/
    public static <T> String printTree(PrintNodeInfo<T> info) {
        if (info == null) {
            return "";
        }
        T node = info.getNode();
        int deep = findDepth(node, info);
        if (deep > info.maxDepth) {
            throw new RuntimeException("太深了, 不好打印啊");
        }
        int size = (1 << deep) - 1;
        List<PrintNode>[] lists = new ArrayList[deep];
        for (int i = 0; i < deep; i++) {
            lists[i] = new ArrayList<>();
        }
        info.setLists(lists);
        addPrint(info, node, size >> 1, 0, deep);
        for (List<PrintNode> list : lists) {
            list.sort((a, b) -> Integer.compare(a.index, b.index));
        }
        StringBuilder all = new StringBuilder();
        for (List<PrintNode> list : lists) {
            StringBuilder sb = new StringBuilder();
            int pre = -1;
            for (PrintNode printNode : list) {
                int index = printNode.index;
                String value = printNode.value;
                int i = index - pre - 1;
                for (int j = 0; j < i; j++) {
                    sb.append(info.getWight());
                }
                pre = index;
                sb.append(value);
            }
            all.append(sb).append(info.line);
        }
        return all.toString();
    }

    /**
     * 前序 遍历,获取节点,并存储到lists中(按层存储,可以层序遍历实现)
     *
     * @param info  树信息
     * @param node  节点
     * @param index 当前元素的坐标为位置
     * @param item  当前深度
     * @param depth 深度
     * @param <T>   树泛型
     */
    private static <T> void addPrint(PrintNodeInfo<T> info, T node, int index, int item, int depth) {
        if (node == null) {
            return;
        }
        List<PrintNode>[] lists = info.getLists();
        List<PrintNode> list = lists[item];
        String s = info.valStr == null ? String.valueOf(info.val.apply(node)) : info.valStr.apply(node);
        PrintNode printNode = new PrintNode(index, s);
        list.add(printNode);
        int w = 1 << (depth - item - 2);
        addPrint(info, info.left.apply(node), index - w, item + 1, depth);
        addPrint(info, info.right.apply(node), index + w, item + 1, depth);
    }

    /**
     * 获取深度
     *
     * @param node 节点
     * @param info 树信息
     * @param <T>  树泛型
     * @return 深度
     */
    private static <T> int findDepth(T node, PrintNodeInfo<T> info) {
        if (node == null) {
            return 0;
        }
        return Math.max(findDepth(info.left.apply(node), info), findDepth(info.right.apply(node), info)) + 1;
    }

    /**
     * 打印的信息
     */
    @Data
    private static class PrintNode {
        // 所处的坐标
        private int index;
        // 打印的内容
        private String value;

        public PrintNode() {
        }

        private PrintNode(int index, String value) {
            this.index = index;
            this.value = value;
        }
    }

    /**
     * 存储一棵树的信息
     *
     * @param <T> 树节点的泛型
     */
    @Data
    public static class PrintNodeInfo<T> {

        // 跟节点
        private T node;

        // 获取当前值,默认为Integer类型,若是其他的类型,则需要修改
        private Function<T, Integer> val;

        // 获取左节点
        private Function<T, T> left;

        // 获取右节点
        private Function<T, T> right;

        // 层序遍历时,存储每一层的节点
        private List<PrintNode>[] lists;

        // 打印的值,若没有设置,则取val(自定义用)
        private Function<T, String> valStr;

        // 占位符,若节点为null,则需要空格占位(长度可以自定义)。
        private String wight = "     ";

        // 每一层拼接换行符,(可以自定义)
        private String line = "\n\n";

        // 若是树深大于此值,则抛出异常,防止打印的过大(默认8,可自定义)
        private int maxDepth = 8;

        public PrintNodeInfo() {
        }

        public PrintNodeInfo(T node, Function<T, Integer> val, Function<T, T> left, Function<T, T> right) {
            this.node = node;
            this.val = val;
            this.left = left;
            this.right = right;
        }
    }

}
PrintNodeInfo类是记录树的所有信息,此处使用lambda表达式实现的,便可以使所有的树形结构都可以使用。
使用方式:
import lombok.Data;

/**
 * @author wzgl
 * @version 1.0
 * @date 2023/5/30 16:05
 */
public class PrintTreeTest {

    public static void main(String[] args) {
        Node a = new Node(110);
        Node b = new Node(105);
        Node c = new Node(115);
        Node d = new Node(102);
        Node e = new Node(107);
        Node f = new Node(112);
        Node g = new Node(117);
        a.left = b;
        a.right = c;
        b.left = d;
        b.right = e;
        c.left = f;
        c.right = g;

        PrintTree.PrintNodeInfo<Node> info = new PrintTree.PrintNodeInfo<>(a, Node::getVal, Node::getLeft, Node::getRight);
        System.out.println(PrintTree.printTree(info));
    }

    @Data
    private static class Node {
        private int val;
        private Node left;
        private Node right;

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

}

 

需要注意的是,因为都是依靠占位符来调整打印数据格式,当使用了不合理的长度的占位符的时候,可能会出现格式错乱。因为只是为了调试用,所以能大体看出树形结构即可。

自定义某些参数

        PrintTree.PrintNodeInfo<Node> info = new PrintTree.PrintNodeInfo<>(a, Node::getVal, Node::getLeft, Node::getRight);
       // 占位符长度
        info.setPlaceHolder("     ");
        // 打印的内容
        info.setValStr(n -> "$" + n.val + "$");
        // 每一层结尾拼接
        info.setLine("\n\n-->");
        System.out.println(PrintTree.printTree(info));