【问题标题】:Coding a basic pretty printer for trees in Java用 Java 为树编写一个基本的漂亮打印机
【发布时间】:2012-02-16 08:50:31
【问题描述】:

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

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

我知道,在前序遍历期间,所有的美感都会在这里实现。

只是不知道如何开始。我得到了一个有效的预购遍历函数,但我不确定从哪里开始修改它,或者我是否应该创建自己的函数。

没有代码,只是询问其他人的想法。

如果我绝望了,也许以后的代码:P

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

【问题讨论】:

标签: java binary-tree pretty-print


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    其实上面这段没关系。您只需要树的深度,即要显示的最大级别。但是,如果树是稀疏的,即并非最后一层和上一层的所有元素都存在,您需要获取正在渲染的节点的位置,以便相应地调整缩进/间距。

    在您的预购迭代中,您可以计算每个级别的元素之间的缩进和间距。缩进的公式是:2(max level - level) - 1 和间距:2(max level - level + 1 ) - 1 (level is 1-based)

    例子:

           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 = 23 - 1 = 8 - 1 = 7)
      • 第一层,所以间距无关紧要
    • 2 级:
      • indent = 3: (2(4-2) - 1 = 22 - 1 = 4 - 1 = 3)
      • spacing = 7 (2(4-1) - 1 = 23 - 1 = 8 - 1 = 7)
    • 3 级:
      • indent = 1: (2(4-3) - 1 = 21 - 1 = 2 - 1 = 1)
      • spacing = 3 (2(4-2) - 1 = 22 - 1 = 4 - 1 = 3)
    • 4 级:
      • indent = 0: (2(4-4) - 1 = 20 - 1 = 1 - 1 = 0)
      • spacing = 1: (2(4-3) - 1 = 21 - 1 = 2 - 1 = 1)

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

    编辑:对于漂亮的打印,您只需将缩进计算为width element * level,其中级别将从零开始。然后如果节点不是叶子,则在前面画一个左括号,在画完孩子之后画一个右括号,如果是叶子就画它,如果叶子不见了,画双括号。

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

    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("()");
      }
    }
    

    【讨论】:

    • 也不错。有点难以理解,但我认为你可能有最好的解决方案。你能再改一下吗……简单地说:P
    • 哦,你在做那种漂亮的印刷品。对不起,我应该指定。我的意思是: (a (bde ) (c () (f (g () h ) () ) ) 用于一棵树,其前序遍历为:abdcfgh 树看起来像:abcdefgh
    • 哇,根本没有格式化。给我一点时间来放一张照片。
    • 谢谢你的帮助,你能不能看一下我发的图片,最后再发一次。我非常感谢。
    • @neojb1989 你看到我的编辑了吗?它应该产生你想要的东西(虽然我给你留下了一些代码,但它毕竟是一个大纲)。
    【解决方案3】:

    这适用于所有输入并打印一行以链接,如下图所示

    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;
            }
        }
    }
    

    【讨论】:

    • 这是金子!我没有将它用于任何真实(即产品)用例,所以我不在乎它是否快速......它看起来很棒!非常感谢!
    【解决方案4】:

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

    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 做同样的事情?

    假设 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);
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多