在Java中为树构建一个基本的漂亮打印机

时间:2012-01-22 20:10:35

标签: java binary-tree pretty-print

我需要帮助来理解如何为任何给定的二叉树编写漂亮的打印机。

我知道第一步是预先订购以获取所有节点。

我知道在前序遍历中,这就是所有漂亮的实现。

只是不确定如何开始。我有一个前序遍历功能,但我不知道从哪里开始修改它,或者我应该自己做功能。

没有代码,只是询问有关别人如何处理它的想法。

如果我绝望的话可能是后来的代码:P

具体来说,它应该是这样的:

example

4 个答案:

答案 0 :(得分:9)

如果您知道树的深度,即水平数,您可以计算最后一级中的最大节点数(n 2 ,n =级别数 - 1,1元素,如果你只有根)。如果您进一步知道元素的宽度(例如,如果您最多有2位数字,则每个元素的宽度为2),您可以计算最后一个级别的宽度: ((number of elements - 1) * (element width + spacing) + element width)

实际上上段并不重要。您只需要树的深度,即要显示的最大级别。但是,如果树是稀疏的,即不存在最后一级和上面的所有元素,则需要获取正在渲染的节点的位置,以便相应地调整该情况的缩进/间距。

在预订迭代中,您可以计算每个级别的元素之间的缩进和间距。缩进的公式为: 2 (最高级别 - 级别) - 1 和间距: 2 (最高级别 - 级别+ 1 ) - 1 (级别为1)

示例:

       1
   2       3
 4   5   6   7
8 9 A B C D E F

在该树中,级别数为4.最后一级的间距为1,而缩进为0.您将获得以下值:

  • 第1级:
    • indent = 7 :( 2 (4-1) - 1 = 2 3 - 1 = 8 - 1 = 7)
    • 第一级,所以间距无关紧要
  • 第2级:
    • indent = 3 :( 2 (4-2) - 1 = 2 2 - 1 = 4 - 1 = 3)
    • spacing = 7(2 (4-1) - 1 = 2 3 - 1 = 8 - 1 = 7)
  • 第3级:
    • indent = 1 :( 2 (4-3) - 1 = 2 1 - 1 = 2 - 1 = 1)
    • spacing = 3(2 (4-2) - 1 = 2 2 - 1 = 4 - 1 = 3)
  • 第4级:
    • indent = 0 :( 2 (4-4) - 1 = 2 0 - 1 = 1 - 1 = 0)
    • spacing = 1 :( 2 (4-3) - 1 = 2 1 - 1 = 2 - 1 = 1)

请注意,在最后一级,您将始终具有间距1 *元素宽度。因此,对于最大元素宽度为3(例如3位数字),您在最后一级的间距为3,以便获得较高级别的一些非常对齐。

编辑:对于漂亮的打印,您只需将缩进计算为width element * level,其中级别将从零开始。然后,如果节点不是叶子,则在绘制子项后使用前面的开头paranthesis和一个闭合的paranthesis绘制它,如果它是一个叶子只是绘制它并且如果缺少叶子,则绘制双重paranthis。

因此你会得到这样的东西:

public void printSubtree( int indent, node ) {
  for( int i = 0; i < indent; ++i) {
    System.out.print(" ");
  }

  if( inner node) {
    System.out.println("(" + value);     
    printSubtree(indent + elem width, left child); //this is a recursive call, alternatively use the indent formula above if you don't use recursion
    printSubtree(indent + elem width, right child);

    //we have a new line so print the indent again
    for( int i = 0; i < indent; ++i) {
      System.out.print(" ");
    }
    System.out.println(")"); 
  } else if( not empty) {
    System.out.println(value);
  } else { //empty/non existing node
    System.out.println("()");
  }
}

答案 1 :(得分:5)

这适用于所有输入并打印一条链接线,如下图所示 enter image description here

package com.sai.samples;

/**
 * @author Saiteja Tokala
 */
import java.util.ArrayList;
import java.util.List;


/**
 * Binary tree printer
 *
 * @author saiteja
 */
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;
        }
    }
}

答案 2 :(得分:1)

由于遍历函数是递归的,因此您可以将格式化参数(例如缩进每个节点的空格数)传递给递归调用。例如。如果要为树中的每个级别缩进2个空格,可以使用参数0开始递归,然后在每个递归步骤中添加2。

答案 3 :(得分:0)

假设您的二叉树以下列方式表示:

public class BinaryTreeModel {

    private Object value;
    private BinaryTreeModel left;
    private BinaryTreeModel right;

    public BinaryTreeModel(Object value) {
        this.value = value;
    }

    // standard getters and setters

}

那么漂亮打印树的代码可以这样写:

对于根节点:

public String traversePreOrder(BinaryTreeModel root) {

    if (root == null) {
        return "";
    }

    StringBuilder sb = new StringBuilder();
    sb.append(root.getValue());

    String pointerRight = "└──";
    String pointerLeft = (root.getRight() != null) ? "├──" : "└──";

    traverseNodes(sb, "", pointerLeft, root.getLeft(), root.getRight() != null);
    traverseNodes(sb, "", pointerRight, root.getRight(), false);

    return sb.toString();
}

对于子节点:

public void traverseNodes(StringBuilder sb, String padding, String pointer, BinaryTreeModel node, 
  boolean hasRightSibling) {
    if (node != null) {
        sb.append("\n");
        sb.append(padding);
        sb.append(pointer);
        sb.append(node.getValue());

        StringBuilder paddingBuilder = new StringBuilder(padding);
        if (hasRightSibling) {
            paddingBuilder.append("│  ");
        } else {
            paddingBuilder.append("   ");
        }

        String paddingForBoth = paddingBuilder.toString();
        String pointerRight = "└──";
        String pointerLeft = (node.getRight() != null) ? "├──" : "└──";

        traverseNodes(sb, paddingForBoth, pointerLeft, node.getLeft(), node.getRight() != null);
        traverseNodes(sb, paddingForBoth, pointerRight, node.getRight(), false);
    }
}

然后你需要做的就是像这样调用根节点的函数:

System.out.println(traversePreOrder(root));

本文详细解释了整个方法 - https://www.baeldung.com/java-print-binary-tree-diagram。这个想法可以扩展到漂亮的打印 generic treestries 等等。

如何对特里做同样的事情?

假设 trie 的辅助类是 TrieTrieNode,定义如下:

public class TrieNode{
    public HashMap<Character, TrieNode> children;
    public boolean isWord;
    public TrieNode() {
        children = new HashMap<>();
        isWord = false;
    }
}

public class Trie{
    public TrieNode root;
    public Trie() {
        root = new TrieNode();
    }
    // Insert method for trie, search method for trie, delete method for trie not shown as they are not relevant
}

现在,要实现漂亮的打印方法,只需在 Trie 类中插入两个方法:

public String traversePreOrder() {
        if (root == null) {
            return "";
        }

        StringBuilder sb = new StringBuilder();
        sb.append("○");

        Set<Character> childNodeChars = root.children.keySet();
        Iterator<Character> iterator = childNodeChars.iterator();
        while (iterator.hasNext()) {
            Character childC = iterator.next();
            if (!iterator.hasNext())
                traverseNodes(sb, "", "└──", root, childC, false);
            else
                traverseNodes(sb, "", "├──", root, childC, true);
        }

        return sb.toString();
    }

    private void traverseNodes(StringBuilder sb, String padding, String pointer, TrieNode node, Character c, boolean hasNextSibling) {
        sb.append("\n");
        sb.append(padding);
        sb.append(pointer);
        sb.append(c);
        sb.append(node.children.get(c).isWord?"(1)":"");

        StringBuilder paddingBuilder = new StringBuilder(padding);
        paddingBuilder.append(hasNextSibling?"│  ":"   ");

        String paddingForBoth = paddingBuilder.toString();

        TrieNode childNode = node.children.get(c);
        if(childNode == null)
            return;

        Set<Character> childNodeChars = childNode.children.keySet();
        Iterator<Character> iterator = childNodeChars.iterator();
        while (iterator.hasNext()) {
            Character childC = iterator.next();
            if (!iterator.hasNext())
                traverseNodes(sb, paddingForBoth, "└──", childNode, childC, false);
            else
                traverseNodes(sb, paddingForBoth, "├──", childNode, childC, true);
        }
    }