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

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

例如

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 树泛型

* @return 树

*/

public static String printTree(PrintNodeInfo 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[] 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 list : lists) {

list.sort((a, b) -> Integer.compare(a.index, b.index));

}

StringBuilder all = new StringBuilder();

for (List 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 树泛型

*/

private static void addPrint(PrintNodeInfo info, T node, int index, int item, int depth) {

if (node == null) {

return;

}

List[] lists = info.getLists();

List 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 树泛型

* @return 深度

*/

private static int findDepth(T node, PrintNodeInfo 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 树节点的泛型

*/

@Data

public static class PrintNodeInfo {

// 跟节点

private T node;

// 获取当前值,默认为Integer类型,若是其他的类型,则需要修改

private Function val;

// 获取左节点

private Function left;

// 获取右节点

private Function right;

// 层序遍历时,存储每一层的节点

private List[] lists;

// 打印的值,若没有设置,则取val(自定义用)

private Function valStr;

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

private String wight = " ";

// 每一层拼接换行符,(可以自定义)

private String line = "\n\n";

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

private int maxDepth = 8;

public PrintNodeInfo() {

}

public PrintNodeInfo(T node, Function val, Function left, Function 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 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 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));

Back to top: