打印树形结构(可视化二叉树)
平时开发时,偶尔会操作二叉树,而查看二叉树的结构,是一种比较费时的事情,我们可以把它按照本身的结构打印出来,从而方便查看。
例如
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));