如何在Java中打印二叉树图?

我如何在Java中打印一个二叉树,这样输出就像:

   4
/ \
2   5

我的节点:

public class Node<A extends Comparable> {
Node<A> left, right;
A data;


public Node(A data){
this.data = data;
}
}
291643 次浏览

你的树每一层需要两倍的距离:

       a
/ \
/   \
/     \
/       \
b       c
/ \     / \
/   \   /   \
d   e   f   g
/ \ / \ / \ / \
h i j k l m n o

你可以将你的树保存在一个数组的数组中,每个数组对应一个深度:

[[a],[b,c],[d,e,f,g],[h,i,j,k,l,m,n,o]]

If your tree is not full, you need to include empty values in that array:

       a
/ \
/   \
/     \
/       \
b       c
/ \     / \
/   \   /   \
d   e   f   g
/ \   \ / \   \
h i   k l m   o
[[a],[b,c],[d,e,f,g],[h,i, ,k,l,m, ,o]]
然后你可以遍历数组来打印你的树,根据深度打印第一个元素之前和元素之间的空格,根据下一层数组中对应的元素是否被填充打印行。 如果你的值可以超过一个字符长,你需要在创建数组表示时找到最长的值,并相应地乘以所有宽度和行数

我已经创建了简单的二叉树打印机。您可以随心所欲地使用和修改它,但无论如何它都没有优化。我认为这里有很多东西可以改进;)

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class BTreePrinterTest {


private static Node<Integer> test1() {
Node<Integer> root = new Node<Integer>(2);
Node<Integer> n11 = new Node<Integer>(7);
Node<Integer> n12 = new Node<Integer>(5);
Node<Integer> n21 = new Node<Integer>(2);
Node<Integer> n22 = new Node<Integer>(6);
Node<Integer> n23 = new Node<Integer>(3);
Node<Integer> n24 = new Node<Integer>(6);
Node<Integer> n31 = new Node<Integer>(5);
Node<Integer> n32 = new Node<Integer>(8);
Node<Integer> n33 = new Node<Integer>(4);
Node<Integer> n34 = new Node<Integer>(5);
Node<Integer> n35 = new Node<Integer>(8);
Node<Integer> n36 = new Node<Integer>(4);
Node<Integer> n37 = new Node<Integer>(5);
Node<Integer> n38 = new Node<Integer>(8);


root.left = n11;
root.right = n12;


n11.left = n21;
n11.right = n22;
n12.left = n23;
n12.right = n24;


n21.left = n31;
n21.right = n32;
n22.left = n33;
n22.right = n34;
n23.left = n35;
n23.right = n36;
n24.left = n37;
n24.right = n38;


return root;
}


private static Node<Integer> test2() {
Node<Integer> root = new Node<Integer>(2);
Node<Integer> n11 = new Node<Integer>(7);
Node<Integer> n12 = new Node<Integer>(5);
Node<Integer> n21 = new Node<Integer>(2);
Node<Integer> n22 = new Node<Integer>(6);
Node<Integer> n23 = new Node<Integer>(9);
Node<Integer> n31 = new Node<Integer>(5);
Node<Integer> n32 = new Node<Integer>(8);
Node<Integer> n33 = new Node<Integer>(4);


root.left = n11;
root.right = n12;


n11.left = n21;
n11.right = n22;


n12.right = n23;
n22.left = n31;
n22.right = n32;


n23.left = n33;


return root;
}


public static void main(String[] args) {


BTreePrinter.printNode(test1());
BTreePrinter.printNode(test2());


}
}


class Node<T extends Comparable<?>> {
Node<T> left, right;
T data;


public Node(T data) {
this.data = data;
}
}


class BTreePrinter {


public static <T extends Comparable<?>> void printNode(Node<T> root) {
int maxLevel = BTreePrinter.maxLevel(root);


printNodeInternal(Collections.singletonList(root), 1, maxLevel);
}


private static <T extends Comparable<?>> void printNodeInternal(List<Node<T>> nodes, int level, int maxLevel) {
if (nodes.isEmpty() || BTreePrinter.isAllElementsNull(nodes))
return;


int floor = maxLevel - level;
int endgeLines = (int) Math.pow(2, (Math.max(floor - 1, 0)));
int firstSpaces = (int) Math.pow(2, (floor)) - 1;
int betweenSpaces = (int) Math.pow(2, (floor + 1)) - 1;


BTreePrinter.printWhitespaces(firstSpaces);


List<Node<T>> newNodes = new ArrayList<Node<T>>();
for (Node<T> node : nodes) {
if (node != null) {
System.out.print(node.data);
newNodes.add(node.left);
newNodes.add(node.right);
} else {
newNodes.add(null);
newNodes.add(null);
System.out.print(" ");
}


BTreePrinter.printWhitespaces(betweenSpaces);
}
System.out.println("");


for (int i = 1; i <= endgeLines; i++) {
for (int j = 0; j < nodes.size(); j++) {
BTreePrinter.printWhitespaces(firstSpaces - i);
if (nodes.get(j) == null) {
BTreePrinter.printWhitespaces(endgeLines + endgeLines + i + 1);
continue;
}


if (nodes.get(j).left != null)
System.out.print("/");
else
BTreePrinter.printWhitespaces(1);


BTreePrinter.printWhitespaces(i + i - 1);


if (nodes.get(j).right != null)
System.out.print("\\");
else
BTreePrinter.printWhitespaces(1);


BTreePrinter.printWhitespaces(endgeLines + endgeLines - i);
}


System.out.println("");
}


printNodeInternal(newNodes, level + 1, maxLevel);
}


private static void printWhitespaces(int count) {
for (int i = 0; i < count; i++)
System.out.print(" ");
}


private static <T extends Comparable<?>> int maxLevel(Node<T> node) {
if (node == null)
return 0;


return Math.max(BTreePrinter.maxLevel(node.left), BTreePrinter.maxLevel(node.right)) + 1;
}


private static <T> boolean isAllElementsNull(List<T> list) {
for (Object object : list) {
if (object != null)
return false;
}


return true;
}


}

输出1:

         2
/ \
/   \
/     \
/       \
7       5
/ \     / \
/   \   /   \
2   6   3   6
/ \ / \ / \ / \
5 8 4 5 8 4 5 8

输出2:

       2
/ \
/   \
/     \
/       \
7       5
/ \       \
/   \       \
2   6       9
/ \     /
5 8     4

按行打印[大]树。

输出的例子:

z
├── c
│   ├── a
│   └── b
├── d
├── e
│   └── asdf
└── f

代码:

public class TreeNode {


final String name;
final List<TreeNode> children;


public TreeNode(String name, List<TreeNode> children) {
this.name = name;
this.children = children;
}


public String toString() {
StringBuilder buffer = new StringBuilder(50);
print(buffer, "", "");
return buffer.toString();
}


private void print(StringBuilder buffer, String prefix, String childrenPrefix) {
buffer.append(prefix);
buffer.append(name);
buffer.append('\n');
for (Iterator<TreeNode> it = children.iterator(); it.hasNext();) {
TreeNode next = it.next();
if (it.hasNext()) {
next.print(buffer, childrenPrefix + "├── ", childrenPrefix + "│   ");
} else {
next.print(buffer, childrenPrefix + "└── ", childrenPrefix + "    ");
}
}
}
}

附注:这个答案并不完全关注“二叉”树——相反,它打印了各种类型的树。解决方案的灵感来自linux中的“树”命令。

迈克尔。克鲁兹曼,我不得不说,这人不错。这很有用。

然而,上面的方法只适用于个位数:如果您要使用多个数字,结构将会错位,因为您使用的是空格而不是制表符。

至于我后来的代码,我需要更多的数字,所以我自己编写了一个程序。

它现在有一些bug,现在我感觉很懒去纠正它们,但它打印得非常漂亮,节点可以接受更大数量的数字。

这棵树不会像问题提到的那样,但它旋转了270度:)

public static void printBinaryTree(TreeNode root, int level){
if(root==null)
return;
printBinaryTree(root.right, level+1);
if(level!=0){
for(int i=0;i<level-1;i++)
System.out.print("|\t");
System.out.println("|-------"+root.val);
}
else
System.out.println(root.val);
printBinaryTree(root.left, level+1);
}

将此函数与您自己指定的TreeNode一起放置,并保持初始级别为0,并享受!

以下是一些输出示例:

|       |       |-------11
|       |-------10
|       |       |-------9
|-------8
|       |       |-------7
|       |-------6
|       |       |-------5
4
|       |-------3
|-------2
|       |-------1




|       |       |       |-------10
|       |       |-------9
|       |-------8
|       |       |-------7
|-------6
|       |-------5
4
|       |-------3
|-------2
|       |-------1

唯一的问题是延伸的分支;我会尽快解决这个问题,但在此之前你也可以使用它。

我需要在我的一个项目中打印一个二叉树,为此我准备了一个java类TreePrinter,其中一个示例输出是:

                [+]
/   \
/     \
/       \
/         \
/           \
[*]             \
/   \             [-]
[speed]     [2]         /   \
[45]     [12]

下面是类TreePrinter和类TextNode的代码。要打印任何树,你可以用TextNode类创建一个等效的树。


import java.util.ArrayList;


public class TreePrinter {


public TreePrinter(){
}


public static String TreeString(TextNode root){
ArrayList layers = new ArrayList();
ArrayList bottom = new ArrayList();


FillBottom(bottom, root);  DrawEdges(root);


int height = GetHeight(root);
for(int i = 0; i  s.length()) min = s.length();


if(!n.isEdge) s += "[";
s += n.text;
if(!n.isEdge) s += "]";


layers.set(n.depth, s);
}


StringBuilder sb = new StringBuilder();


for(int i = 0; i  temp = new ArrayList();


for(int i = 0; i  0) temp.get(i-1).left = x;
temp.add(x);
}


temp.get(count-1).left = n.left;
n.left.depth = temp.get(count-1).depth+1;
n.left = temp.get(0);


DrawEdges(temp.get(count-1).left);
}
if(n.right != null){
int count = n.right.x - (n.x + n.text.length() + 2);
ArrayList temp = new ArrayList();


for(int i = 0; i  0) temp.get(i-1).right = x;
temp.add(x);
}


temp.get(count-1).right = n.right;
n.right.depth = temp.get(count-1).depth+1;
n.right = temp.get(0);


DrawEdges(temp.get(count-1).right);
}
}


private static void FillBottom(ArrayList bottom, TextNode n){
if(n == null) return;


FillBottom(bottom, n.left);


if(!bottom.isEmpty()){
int i = bottom.size()-1;
while(bottom.get(i).isEdge) i--;
TextNode last = bottom.get(i);


if(!n.isEdge) n.x = last.x + last.text.length() + 3;
}
bottom.add(n);
FillBottom(bottom, n.right);
}


private static boolean isLeaf(TextNode n){
return (n.left == null && n.right == null);
}


private static int GetHeight(TextNode n){
if(n == null) return 0;


int l = GetHeight(n.left);
int r = GetHeight(n.right);


return Math.max(l, r) + 1;
}
}




class TextNode {
public String text;
public TextNode parent, left, right;
public boolean isEdge;
public int x, depth;


public TextNode(String text){
this.text = text;
parent = null; left = null; right = null;
isEdge = false;
x = 0; depth = 0;
}
}

最后,这里是一个打印给定样本的测试类:


public class Test {


public static void main(String[] args){
TextNode root = new TextNode("+");
root.left = new TextNode("*");            root.left.parent = root;
root.right = new TextNode("-");           root.right.parent = root;
root.left.left = new TextNode("speed");   root.left.left.parent = root.left;
root.left.right = new TextNode("2");      root.left.right.parent = root.left;
root.right.left = new TextNode("45");     root.right.left.parent = root.right;
root.right.right = new TextNode("12");    root.right.right.parent = root.right;


System.out.println(TreePrinter.TreeString(root));
}
}
public static class Node<T extends Comparable<T>> {
T value;
Node<T> left, right;


public void insertToTree(T v) {
if (value == null) {
value = v;
return;
}
if (v.compareTo(value) < 0) {
if (left == null) {
left = new Node<T>();
}
left.insertToTree(v);
} else {
if (right == null) {
right = new Node<T>();
}
right.insertToTree(v);
}
}


public void printTree(OutputStreamWriter out) throws IOException {
if (right != null) {
right.printTree(out, true, "");
}
printNodeValue(out);
if (left != null) {
left.printTree(out, false, "");
}
}
private void printNodeValue(OutputStreamWriter out) throws IOException {
if (value == null) {
out.write("<null>");
} else {
out.write(value.toString());
}
out.write('\n');
}
// use string and not stringbuffer on purpose as we need to change the indent at each recursion
private void printTree(OutputStreamWriter out, boolean isRight, String indent) throws IOException {
if (right != null) {
right.printTree(out, true, indent + (isRight ? "        " : " |      "));
}
out.write(indent);
if (isRight) {
out.write(" /");
} else {
out.write(" \\");
}
out.write("----- ");
printNodeValue(out);
if (left != null) {
left.printTree(out, false, indent + (isRight ? " |      " : "        "));
}
}


}

将打印:

                 /----- 20
|       \----- 15
/----- 14
|       \----- 13
/----- 12
|       |       /----- 11
|       \----- 10
|               \----- 9
8
|               /----- 7
|       /----- 6
|       |       \----- 5
\----- 4
|       /----- 3
\----- 2
\----- 1

对于输入

< p > 8 4 12 2 6 10 14 1 3. 5 7 9 11 13 20. 15 < / p >

这是@anurag回答的一个变体——看到额外的|让我很烦

这是打印树的一个非常简单的解决方案。它不是那么漂亮,但它真的很简单:

enum { kWidth = 6 };
void PrintSpace(int n)
{
for (int i = 0; i < n; ++i)
printf(" ");
}


void PrintTree(struct Node * root, int level)
{
if (!root) return;
PrintTree(root->right, level + 1);
PrintSpace(level * kWidth);
printf("%d", root->data);
PrintTree(root->left, level + 1);
}

样例输出:

106
105
104
103
102
101
100
public void printPreety() {
List<TreeNode> list = new ArrayList<TreeNode>();
list.add(head);
printTree(list, getHeight(head));
}


public int getHeight(TreeNode head) {


if (head == null) {
return 0;
} else {
return 1 + Math.max(getHeight(head.left), getHeight(head.right));
}
}


/**
* pass head node in list and height of the tree
*
* @param levelNodes
* @param level
*/
private void printTree(List<TreeNode> levelNodes, int level) {


List<TreeNode> nodes = new ArrayList<TreeNode>();


//indentation for first node in given level
printIndentForLevel(level);


for (TreeNode treeNode : levelNodes) {


//print node data
System.out.print(treeNode == null?" ":treeNode.data);


//spacing between nodes
printSpacingBetweenNodes(level);


//if its not a leaf node
if(level>1){
nodes.add(treeNode == null? null:treeNode.left);
nodes.add(treeNode == null? null:treeNode.right);
}
}
System.out.println();


if(level>1){
printTree(nodes, level-1);
}
}


private void printIndentForLevel(int level){
for (int i = (int) (Math.pow(2,level-1)); i >0; i--) {
System.out.print(" ");
}
}


private void printSpacingBetweenNodes(int level){
//spacing between nodes
for (int i = (int) ((Math.pow(2,level-1))*2)-1; i >0; i--) {
System.out.print(" ");
}
}




Prints Tree in following format:
4
3               7
1               5       8
2                       10
9

改编自Vasya诺维科夫先生回答,使其更二进制,并使用StringBuilder来提高效率(在Java中将String对象连接在一起通常效率很低)。

public StringBuilder toString(StringBuilder prefix, boolean isTail, StringBuilder sb) {
if(right!=null) {
right.toString(new StringBuilder().append(prefix).append(isTail ? "│   " : "    "), false, sb);
}
sb.append(prefix).append(isTail ? "└── " : "┌── ").append(value.toString()).append("\n");
if(left!=null) {
left.toString(new StringBuilder().append(prefix).append(isTail ? "    " : "│   "), true, sb);
}
return sb;
}


@Override
public String toString() {
return this.toString(new StringBuilder(), true, new StringBuilder()).toString();
}

输出:

│       ┌── 7
│   ┌── 6
│   │   └── 5
└── 4
│   ┌── 3
└── 2
└── 1
└── 0

我为此做了一个改进的算法,可以很好地处理不同大小的节点。它使用行自上而下地打印。

package alg;


import java.util.ArrayList;
import java.util.List;




/**
* Binary tree printer
*
* @author MightyPork
*/
public class TreePrinter
{
/** Node that can be printed */
public interface PrintableNode
{
/** Get left child */
PrintableNode getLeft();




/** Get right child */
PrintableNode getRight();




/** Get text to be printed */
String getText();
}




/**
* Print a tree
*
* @param root
*            tree root node
*/
public static void print(PrintableNode root)
{
List<List<String>> lines = new ArrayList<List<String>>();


List<PrintableNode> level = new ArrayList<PrintableNode>();
List<PrintableNode> next = new ArrayList<PrintableNode>();


level.add(root);
int nn = 1;


int widest = 0;


while (nn != 0) {
List<String> line = new ArrayList<String>();


nn = 0;


for (PrintableNode n : level) {
if (n == null) {
line.add(null);


next.add(null);
next.add(null);
} else {
String aa = n.getText();
line.add(aa);
if (aa.length() > widest) widest = aa.length();


next.add(n.getLeft());
next.add(n.getRight());


if (n.getLeft() != null) nn++;
if (n.getRight() != null) nn++;
}
}


if (widest % 2 == 1) widest++;


lines.add(line);


List<PrintableNode> tmp = level;
level = next;
next = tmp;
next.clear();
}


int perpiece = lines.get(lines.size() - 1).size() * (widest + 4);
for (int i = 0; i < lines.size(); i++) {
List<String> line = lines.get(i);
int hpw = (int) Math.floor(perpiece / 2f) - 1;


if (i > 0) {
for (int j = 0; j < line.size(); j++) {


// split node
char c = ' ';
if (j % 2 == 1) {
if (line.get(j - 1) != null) {
c = (line.get(j) != null) ? '┴' : '┘';
} else {
if (j < line.size() && line.get(j) != null) c = '└';
}
}
System.out.print(c);


// lines and spaces
if (line.get(j) == null) {
for (int k = 0; k < perpiece - 1; k++) {
System.out.print(" ");
}
} else {


for (int k = 0; k < hpw; k++) {
System.out.print(j % 2 == 0 ? " " : "─");
}
System.out.print(j % 2 == 0 ? "┌" : "┐");
for (int k = 0; k < hpw; k++) {
System.out.print(j % 2 == 0 ? "─" : " ");
}
}
}
System.out.println();
}


// print line of numbers
for (int j = 0; j < line.size(); j++) {


String f = line.get(j);
if (f == null) f = "";
int gap1 = (int) Math.ceil(perpiece / 2f - f.length() / 2f);
int gap2 = (int) Math.floor(perpiece / 2f - f.length() / 2f);


// a number
for (int k = 0; k < gap1; k++) {
System.out.print(" ");
}
System.out.print(f);
for (int k = 0; k < gap2; k++) {
System.out.print(" ");
}
}
System.out.println();


perpiece /= 2;
}
}
}

为了在你的树中使用它,让你的Node类实现PrintableNode

示例输出:

                                         2952:0
┌───────────────────────┴───────────────────────┐
1249:-1                                         5866:0
┌───────────┴───────────┐                       ┌───────────┴───────────┐
491:-1                  1572:0                  4786:1                  6190:0
┌─────┘                                               └─────┐           ┌─────┴─────┐
339:0                                                      5717:0      6061:0      6271:0
private StringBuilder prettyPrint(Node root, int currentHeight, int totalHeight) {
StringBuilder sb = new StringBuilder();
int spaces = getSpaceCount(totalHeight-currentHeight + 1);
if(root == null) {
//create a 'spatial' block and return it
String row = String.format("%"+(2*spaces+1)+"s%n", "");
//now repeat this row space+1 times
String block = new String(new char[spaces+1]).replace("\0", row);
return new StringBuilder(block);
}
if(currentHeight==totalHeight) return new StringBuilder(root.data+"");
int slashes = getSlashCount(totalHeight-currentHeight +1);
sb.append(String.format("%"+(spaces+1)+"s%"+spaces+"s", root.data+"", ""));
sb.append("\n");
//now print / and \
// but make sure that left and right exists
char leftSlash = root.left == null? ' ':'/';
char rightSlash = root.right==null? ' ':'\\';
int spaceInBetween = 1;
for(int i=0, space = spaces-1; i<slashes; i++, space --, spaceInBetween+=2) {
for(int j=0; j<space; j++) sb.append(" ");
sb.append(leftSlash);
for(int j=0; j<spaceInBetween; j++) sb.append(" ");
sb.append(rightSlash+"");
for(int j=0; j<space; j++) sb.append(" ");
sb.append("\n");
}
//sb.append("\n");


//now get string representations of left and right subtrees
StringBuilder leftTree = prettyPrint(root.left, currentHeight+1, totalHeight);
StringBuilder rightTree = prettyPrint(root.right, currentHeight+1, totalHeight);
// now line by line print the trees side by side
Scanner leftScanner = new Scanner(leftTree.toString());
Scanner rightScanner = new Scanner(rightTree.toString());
//      spaceInBetween+=1;
while(leftScanner.hasNextLine()) {
if(currentHeight==totalHeight-1) {
sb.append(String.format("%-2s %2s", leftScanner.nextLine(), rightScanner.nextLine()));
sb.append("\n");
spaceInBetween-=2;
}
else {
sb.append(leftScanner.nextLine());
sb.append(" ");
sb.append(rightScanner.nextLine()+"\n");
}
}


return sb;


}
private int getSpaceCount(int height) {
return (int) (3*Math.pow(2, height-2)-1);
}
private int getSlashCount(int height) {
if(height <= 3) return height -1;
return (int) (3*Math.pow(2, height-3)-1);
}

https://github.com/murtraja/java-binary-tree-printer

只适用于1到2位整数(我懒得让它通用)

歪 full < / p >

在控制台打印:

                                                500
700                                             300
200                                   400

简单代码:

public int getHeight()
{
if(rootNode == null) return -1;
return getHeight(rootNode);
}


private int getHeight(Node node)
{
if(node == null) return -1;


return Math.max(getHeight(node.left), getHeight(node.right)) + 1;
}


public void printBinaryTree(Node rootNode)
{
Queue<Node> rootsQueue = new LinkedList<Node>();
Queue<Node> levelQueue = new LinkedList<Node>();
levelQueue.add(rootNode);
int treeHeight = getHeight();
int firstNodeGap;
int internalNodeGap;
int copyinternalNodeGap;
while(true)
{
System.out.println("");
internalNodeGap = (int)(Math.pow(2, treeHeight + 1) -1);
copyinternalNodeGap = internalNodeGap;
firstNodeGap = internalNodeGap/2;


boolean levelFirstNode = true;


while(!levelQueue.isEmpty())
{
internalNodeGap = copyinternalNodeGap;
Node currNode = levelQueue.poll();
if(currNode != null)
{
if(levelFirstNode)
{
while(firstNodeGap > 0)
{
System.out.format("%s", "   ");
firstNodeGap--;
}
levelFirstNode =false;
}
else
{
while(internalNodeGap>0)
{
internalNodeGap--;
System.out.format("%s", "   ");
}
}
System.out.format("%3d",currNode.data);
rootsQueue.add(currNode);
}
}


--treeHeight;


while(!rootsQueue.isEmpty())
{
Node currNode = rootsQueue.poll();
if(currNode != null)
{
levelQueue.add(currNode.left);
levelQueue.add(currNode.right);
}
}


if(levelQueue.isEmpty()) break;
}


}
这是一个非常多功能的树打印机。不是最好看的,但能处理很多案子。如果你能弄清楚,可以随意添加斜杠。 enter image description here < / p >
package com.tomac120.NodePrinter;


import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;


/**
* Created by elijah on 6/28/16.
*/
public class NodePrinter{
final private List<List<PrintableNodePosition>> nodesByRow;
int maxColumnsLeft = 0;
int maxColumnsRight = 0;
int maxTitleLength = 0;
String sep = " ";
int depth = 0;


public NodePrinter(PrintableNode rootNode, int chars_per_node){
this.setDepth(rootNode,1);
nodesByRow = new ArrayList<>(depth);
this.addNode(rootNode._getPrintableNodeInfo(),0,0);
for (int i = 0;i<chars_per_node;i++){
//sep += " ";
}
}


private void setDepth(PrintableNode info, int depth){
if (depth > this.depth){
this.depth = depth;
}
if (info._getLeftChild() != null){
this.setDepth(info._getLeftChild(),depth+1);
}
if (info._getRightChild() != null){
this.setDepth(info._getRightChild(),depth+1);
}
}


private void addNode(PrintableNodeInfo node, int level, int position){
if (position < 0 && -position > maxColumnsLeft){
maxColumnsLeft = -position;
}
if (position > 0 && position > maxColumnsRight){
maxColumnsRight = position;
}
if (node.getTitleLength() > maxTitleLength){
maxTitleLength = node.getTitleLength();
}
List<PrintableNodePosition> row = this.getRow(level);
row.add(new PrintableNodePosition(node, level, position));
level++;


int depthToUse = Math.min(depth,6);
int levelToUse = Math.min(level,6);
int offset = depthToUse - levelToUse-1;
offset = (int)(Math.pow(offset,Math.log(depthToUse)*1.4));
offset = Math.max(offset,3);




PrintableNodeInfo leftChild = node.getLeftChildInfo();
PrintableNodeInfo rightChild = node.getRightChildInfo();
if (leftChild != null){
this.addNode(leftChild,level,position-offset);
}
if (rightChild != null){
this.addNode(rightChild,level,position+offset);
}
}


private List<PrintableNodePosition> getRow(int row){
if (row > nodesByRow.size() - 1){
nodesByRow.add(new LinkedList<>());
}
return nodesByRow.get(row);
}


public void print(){
int max_chars = this.maxColumnsLeft+maxColumnsRight+1;
int level = 0;
String node_format = "%-"+this.maxTitleLength+"s";
for (List<PrintableNodePosition> pos_arr : this.nodesByRow){
String[] chars = this.getCharactersArray(pos_arr,max_chars);
String line = "";
int empty_chars = 0;
for (int i=0;i<chars.length+1;i++){
String value_i = i < chars.length ? chars[i]:null;
if (chars.length + 1 == i || value_i != null){
if (empty_chars > 0) {
System.out.print(String.format("%-" + empty_chars + "s", " "));
}
if (value_i != null){
System.out.print(String.format(node_format,value_i));
empty_chars = -1;
} else{
empty_chars = 0;
}
} else {
empty_chars++;
}
}
System.out.print("\n");


int depthToUse = Math.min(6,depth);
int line_offset = depthToUse - level;
line_offset *= 0.5;
line_offset = Math.max(0,line_offset);


for (int i=0;i<line_offset;i++){
System.out.println("");
}




level++;
}
}


private String[] getCharactersArray(List<PrintableNodePosition> nodes, int max_chars){
String[] positions = new String[max_chars+1];
for (PrintableNodePosition a : nodes){
int pos_i = maxColumnsLeft + a.column;
String title_i = a.nodeInfo.getTitleFormatted(this.maxTitleLength);
positions[pos_i] = title_i;
}
return positions;
}
}

NodeInfo类

package com.tomac120.NodePrinter;


/**
* Created by elijah on 6/28/16.
*/
public class PrintableNodeInfo {
public enum CLI_PRINT_COLOR {
RESET("\u001B[0m"),
BLACK("\u001B[30m"),
RED("\u001B[31m"),
GREEN("\u001B[32m"),
YELLOW("\u001B[33m"),
BLUE("\u001B[34m"),
PURPLE("\u001B[35m"),
CYAN("\u001B[36m"),
WHITE("\u001B[37m");


final String value;
CLI_PRINT_COLOR(String value){
this.value = value;
}


@Override
public String toString() {
return value;
}
}
private final String title;
private final PrintableNode leftChild;
private final PrintableNode rightChild;
private final CLI_PRINT_COLOR textColor;


public PrintableNodeInfo(String title, PrintableNode leftChild, PrintableNode rightChild){
this(title,leftChild,rightChild,CLI_PRINT_COLOR.BLACK);
}


public PrintableNodeInfo(String title, PrintableNode leftChild, PrintableNode righthild, CLI_PRINT_COLOR textColor){
this.title = title;
this.leftChild = leftChild;
this.rightChild = righthild;
this.textColor = textColor;
}


public String getTitle(){
return title;
}


public CLI_PRINT_COLOR getTextColor(){
return textColor;
}


public String getTitleFormatted(int max_chars){
return this.textColor+title+CLI_PRINT_COLOR.RESET;
/*
String title = this.title.length() > max_chars ? this.title.substring(0,max_chars+1):this.title;
boolean left = true;
while(title.length() < max_chars){
if (left){
title = " "+title;
} else {
title = title + " ";
}
}
return this.textColor+title+CLI_PRINT_COLOR.RESET;*/
}


public int getTitleLength(){
return title.length();
}


public PrintableNodeInfo getLeftChildInfo(){
if (leftChild == null){
return null;
}
return leftChild._getPrintableNodeInfo();
}


public PrintableNodeInfo getRightChildInfo(){
if (rightChild == null){
return null;
}
return rightChild._getPrintableNodeInfo();
}
}

NodePosition类

package com.tomac120.NodePrinter;


/**
* Created by elijah on 6/28/16.
*/
public class PrintableNodePosition implements Comparable<PrintableNodePosition> {
public final int row;
public final int column;
public final PrintableNodeInfo nodeInfo;
public PrintableNodePosition(PrintableNodeInfo nodeInfo, int row, int column){
this.row = row;
this.column = column;
this.nodeInfo = nodeInfo;
}


@Override
public int compareTo(PrintableNodePosition o) {
return Integer.compare(this.column,o.column);
}
}

最后是节点接口

package com.tomac120.NodePrinter;


/**
* Created by elijah on 6/28/16.
*/
public interface PrintableNode {
PrintableNodeInfo _getPrintableNodeInfo();
PrintableNode _getLeftChild();
PrintableNode _getRightChild();
}

一个Scala解决方案,改编自Vasya Novikov的答案,专门用于二叉树:

/** An immutable Binary Tree. */
case class BTree[T](value: T, left: Option[BTree[T]], right: Option[BTree[T]]) {


/* Adapted from: http://stackoverflow.com/a/8948691/643684 */
def pretty: String = {
def work(tree: BTree[T], prefix: String, isTail: Boolean): String = {
val (line, bar) = if (isTail) ("└── ", " ") else ("├── ", "│")


val curr = s"${prefix}${line}${tree.value}"


val rights = tree.right match {
case None    => s"${prefix}${bar}   ├── ∅"
case Some(r) => work(r, s"${prefix}${bar}   ", false)
}


val lefts = tree.left match {
case None    => s"${prefix}${bar}   └── ∅"
case Some(l) => work(l, s"${prefix}${bar}   ", true)
}


s"${curr}\n${rights}\n${lefts}"


}


work(this, "", true)
}
}

我发现VasyaNovikov的答案对于打印大型通用树非常有用,并将其修改为二叉树

代码:

class TreeNode {
Integer data = null;
TreeNode left = null;
TreeNode right = null;


TreeNode(Integer data) {this.data = data;}


public void print() {
print("", this, false);
}


public void print(String prefix, TreeNode n, boolean isLeft) {
if (n != null) {
System.out.println (prefix + (isLeft ? "|-- " : "\\-- ") + n.data);
print(prefix + (isLeft ? "|   " : "    "), n.left, true);
print(prefix + (isLeft ? "|   " : "    "), n.right, false);
}
}
}

样例输出:

\-- 7
|-- 3
|   |-- 1
|   |   \-- 2
|   \-- 5
|       |-- 4
|       \-- 6
\-- 11
|-- 9
|   |-- 8
|   \-- 10
\-- 13
|-- 12
\-- 14

下面是可视化树的另一种方法:将节点保存为xml文件,然后让浏览器显示层次结构:

class treeNode{
int key;
treeNode left;
treeNode right;


public treeNode(int key){
this.key = key;
left = right = null;
}


public void printNode(StringBuilder output, String dir){
output.append("<node key='" + key + "' dir='" + dir + "'>");
if(left != null)
left.printNode(output, "l");
if(right != null)
right.printNode(output, "r");
output.append("</node>");
}
}


class tree{
private treeNode treeRoot;


public tree(int key){
treeRoot = new treeNode(key);
}


public void insert(int key){
insert(treeRoot, key);
}


private treeNode insert(treeNode root, int key){
if(root == null){
treeNode child = new treeNode(key);
return child;
}


if(key < root.key)
root.left = insert(root.left, key);
else if(key > root.key)
root.right = insert(root.right, key);


return root;
}


public void saveTreeAsXml(){
StringBuilder strOutput = new StringBuilder();
strOutput.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
treeRoot.printNode(strOutput, "root");
try {
PrintWriter writer = new PrintWriter("C:/tree.xml", "UTF-8");
writer.write(strOutput.toString());
writer.close();
}
catch (FileNotFoundException e){


}
catch(UnsupportedEncodingException e){


}
}
}

下面是测试它的代码:

    tree t = new tree(1);
t.insert(10);
t.insert(5);
t.insert(4);
t.insert(20);
t.insert(40);
t.insert(30);
t.insert(80);
t.insert(60);
t.insert(50);


t.saveTreeAsXml();

输出如下所示:

enter image description here

根据VasyaNovikov的回答。改进了一些Java魔术:泛型和函数接口。

/**
* Print a tree structure in a pretty ASCII fromat.
* @param prefix Currnet previx. Use "" in initial call!
* @param node The current node. Pass the root node of your tree in initial call.
* @param getChildrenFunc A {@link Function} that returns the children of a given node.
* @param isTail Is node the last of its sibblings. Use true in initial call. (This is needed for pretty printing.)
* @param <T> The type of your nodes. Anything that has a toString can be used.
*/
private <T> void printTreeRec(String prefix, T node, Function<T, List<T>> getChildrenFunc, boolean isTail) {
String nodeName = node.toString();
String nodeConnection = isTail ? "└── " : "├── ";
log.debug(prefix + nodeConnection + nodeName);
List<T> children = getChildrenFunc.apply(node);
for (int i = 0; i < children.size(); i++) {
String newPrefix = prefix + (isTail ? "    " : "│   ");
printTreeRec(newPrefix, children.get(i), getChildrenFunc, i == children.size()-1);
}
}

初始调用示例:

Function<ChecksumModel, List<ChecksumModel>> getChildrenFunc = node -> getChildrenOf(node)
printTreeRec("", rootNode, getChildrenFunc, true);

将输出如下内容

└── rootNode
├── childNode1
├── childNode2
│   ├── childNode2.1
│   ├── childNode2.2
│   └── childNode2.3
├── childNode3
└── childNode4

这是水平视图最简单的解决方案。我举了很多例子。很适合我的目的。更新自@ ntin -k的回答。

public void print(String prefix, BTNode n, boolean isLeft) {
if (n != null) {
print(prefix + "     ", n.right, false);
System.out.println (prefix + ("|-- ") + n.data);
print(prefix + "     ", n.left, true);
}
}

电话:

bst.print("", bst.root, false);

解决方案:

                         |-- 80
|-- 70
|-- 60
|-- 50
|-- 40
|-- 30
|-- 20
|-- 10
using map...
{
Map<Integer,String> m = new LinkedHashMap<>();


tn.printNodeWithLvl(node,l,m);


for(Entry<Integer, String> map :m.entrySet()) {
System.out.println(map.getValue());
}
then....method




private  void printNodeWithLvl(Node node,int l,Map<Integer,String> m) {
if(node==null) {
return;
}
if(m.containsKey(l)) {
m.put(l, new StringBuilder(m.get(l)).append(node.value).toString());
}else {
m.put(l, node.value+"");
}
l++;
printNodeWithLvl( node.left,l,m);
printNodeWithLvl(node.right,l,m);


}
}

与垂直表示相比,水平表示有点复杂。垂直打印只是简单的RNL(右->节点->左或镜像的顺序)遍历,这样右子树先打印,然后是左子树。

def printFullTree(root, delim=' ', idnt=[], left=None):
if root:
idnt.append(delim)
x, y = setDelims(left)
printFullTree(root.right, x, idnt, False)
indent2(root.val, idnt)
printFullTree(root.left, y, idnt, True)
idnt.pop()


def setDelims(left):
x = ' '; y='|'
return (y,x) if (left == True) else (x,y) if (left == False) else (x,x)


def indent2(x, idnt, width=6):
for delim in idnt:
print(delim + ' '*(width-1), end='')
print('|->', x)
output:
|-> 15
|-> 14
|     |-> 13
|-> 12
|     |     |-> 11
|     |-> 10
|           |-> 9
|-> 8
|           |-> 7
|     |-> 6
|     |     |-> 4
|-> 3
|     |-> 2
|-> 1
|-> 0

在水平表示中,显示由TreeMap或HashMap<Integer, TreeMap<Integer, Object>> xy;的HashMap构建,其中HashMap包含节点的y轴/level_no作为Key, TreeMap作为value。Treemap内部保存同一级别的所有节点,按它们的x轴值排序,作为键,从最左端开始-ve,根=0,最右端=+ve。

如果使用自平衡树/Treap,则使用HashMap使算法在每个级别的O(1)查找中工作,并在O(logn)中使用TreeMap排序。

不过,在这样做的时候,不要忘记为空子存储占位符,例如' '/空格,这样树看起来就像预期的那样。

现在唯一剩下的就是计算水平节点的距离,这可以用一些数学计算来完成,

  1. 计算树的宽度和高度。
  2. 一旦完成,在显示节点时,根据计算的宽度,高度和倾斜信息(如果有的话),以最佳距离呈现它们。

https://github.com/AharonSambol/PrettyPrintTreeJava

我知道我迟到了。但是我做了这个解决方案,不仅适用于简单的树,也适用于更复杂的树(如多行字符串)

示例输出:

example output

试试这个:

public static void print(int[] minHeap, int minWidth) {


int size = minHeap.length;


int level = log2(size);
int maxLength = (int) Math.pow(2, level) * minWidth;
int currentLevel = -1 ;
int width = maxLength;


for (int i = 0; i < size; i++) {
if (log2(i + 1) > currentLevel) {
currentLevel++;
System.out.println();
width = maxLength / (int) Math.pow(2, currentLevel);
}
System.out.print(StringUtils.center(String.valueOf(minHeap[i]), width));
}
System.out.println();
}


private static int log2(int n) {
return (int) (Math.log(n) / Math.log(2));
}

这段代码片段的思想是用maxLength(即底线的长度)除以每一行的元素数量来得到块宽度。然后把元素放在每个块的中间。

参数minWidth表示底部行中块的长度。

说明思想和显示结果的图片