【问题标题】:How do I print out a tree structure?如何打印出树状结构?
【发布时间】:2009-10-30 10:29:24
【问题描述】:

我正在努力提高我们应用的性能。我以调用树的形式获得了性能信息,具有以下节点类:

public class Node
{
    public string Name; // method name
    public decimal Time; // time spent in method
    public List<Node> Children;
}

我想打印出树,以便我可以看到节点之间的线 - 类似于this question。我可以在 C# 中使用什么算法来做到这一点?

编辑:显然我需要使用递归 - 但我的尝试不断将行放在错误的位置。我要的是一种特定的算法,它将以一种很好的方式打印树 - 何时打印垂直线以及何时打印水平线的详细信息。

编辑:仅使用字符串的副本来缩进节点是不够的。我不是在找

A
|-B
|-|-C
|-|-D
|-|-|-E
|-F
|-|-G

应该是的

A
+-B
| +-C
| +-D
|   +-E
+-F
  +-G

或任何类似的东西,只要树结构是可见的。请注意,C 和 D 的缩进与 G 不同 - 我不能只使用重复的字符串来缩进节点。

【问题讨论】:

标签: c# .net tree pretty-print


【解决方案1】:

诀窍是传递一个字符串作为缩进并特别对待最后一个孩子:

class Node
{    
   public void PrintPretty(string indent, bool last)
   {
       Console.Write(indent);
       if (last)
       {
           Console.Write("\\-");
           indent += "  ";
       }
       else
       {
           Console.Write("|-");
           indent += "| ";
       }
       Console.WriteLine(Name);

       for (int i = 0; i < Children.Count; i++)
           Children[i].PrintPretty(indent, i == Children.Count - 1);
   }
}

如果这样调用:

root.PrintPretty("", true);

将以这种风格输出:

\-root
  \-child
    |-child
    \-child
      |-child
      |-child
      \-child
        |-child
        |-child
        | |-child
        | \-child
        |   |-child
        |   |-child
        |   |-child
        |   \-child
        |     \-child
        |       \-child
        \-child
          |-child
          |-child
          |-child
          | \-child
          \-child
            \-child

【讨论】:

  • 我想不通的是如何没有初始的\-(或|-- ,或└──,取决于你的ascii偏好)。
  • 如果有人有兴趣替换 @IanBoyd 提到的“\-”等,您可以这样做:stackoverflow.com/a/36313190/831138
【解决方案2】:

带递归

您需要跟踪缩进字符串,该缩进字符串会随着您深入到树中而被修改。为避免添加额外的 | 字符,您还需要知道 Node 是否是该集合中的最后一个子节点。

public static void PrintTree(Node tree, String indent, Bool last)
{
    Console.Write(indent + "+- " + tree.Name);
    indent += last ? "   " : "|  ";

    for (int i = 0; i < tree.Children.Count; i++)
    {
        PrintTree(tree.Children[i], indent, i == tree.Children.Count - 1);
    }
}

当这样调用时:

PrintTree(node, "", true)

它会输出如下文本:

+- root
   +- branch-A
   |  +- sibling-X
   |  |  +- grandchild-A
   |  |  +- grandchild-B
   |  +- sibling-Y
   |  |  +- grandchild-C
   |  |  +- grandchild-D
   |  +- sibling-Z
   |     +- grandchild-E
   |     +- grandchild-F
   +- branch-B
      +- sibling-J
      +- sibling-K

没有递归

如果你碰巧有一棵非常深的树并且你的调用堆栈大小是有限的,你可以做一个静态的、非递归的树遍历来输出相同的结果:

public static void PrintTree(Node tree)
{
    List<Node> firstStack = new List<Node>();
    firstStack.Add(tree);

    List<List<Node>> childListStack = new List<List<Node>>();
    childListStack.Add(firstStack);

    while (childListStack.Count > 0)
    {
        List<Node> childStack = childListStack[childListStack.Count - 1];

        if (childStack.Count == 0)
        {
            childListStack.RemoveAt(childListStack.Count - 1);
        }
        else
        {
            tree = childStack[0];
            childStack.RemoveAt(0);

            string indent = "";
            for (int i = 0; i < childListStack.Count - 1; i++)
            {
                indent += (childListStack[i].Count > 0) ? "|  " : "   ";
            }

            Console.WriteLine(indent + "+- " + tree.Name);

            if (tree.Children.Count > 0)
            {
                childListStack.Add(new List<Node>(tree.Children));
            }
        }
    }
}

【讨论】:

  • 非常漂亮和优雅。我根据您的回答制作了一个通用版本。
【解决方案3】:

创建 PrintNode 方法并使用递归:

class Node
{
    public string Name;
    public decimal Time;
    public List<Node> Children = new List<Node>();

    public void PrintNode(string prefix)
    {
        Console.WriteLine("{0} + {1} : {2}", prefix, this.Name, this.Time);
        foreach (Node n in Children)
            if (Children.IndexOf(n) == Children.Count - 1)
                n.PrintNode(prefix + "    ");
            else
                n.PrintNode(prefix + "   |");
    }
}

然后打印整个树只需执行:

topNode.PrintNode("");

在我的例子中,它会给我们类似的东西:

 + top : 123
   | + Node 1 : 29
   |   | + subnode 0 : 90
   |   |     + sdhasj : 232
   |   | + subnode 1 : 38
   |   | + subnode 2 : 49
   |   | + subnode 8 : 39
   |     + subnode 9 : 47
     + Node 2 : 51
       | + subnode 0 : 89
       |     + sdhasj : 232
       | + subnode 1 : 33
         + subnode 3 : 57

【讨论】:

  • 这不会绘制树,它只是使用“|--”的副本缩进。我希望能够看到树,所以算法必须留下空隙。
  • 对不起,我不明白你在说什么。它的作用类似于您给出的示例。你到底想达到什么目的?什么“差距”?
  • 好的,我给你举了另一个例子。我写的只是主要概念,你自己想一想,然后用它来达到你的目的。想法是一样的,但你只需要想出好的、“聪明”的方式来使用它。祝你好运!
  • 因此您需要添加一些例程,这些例程将为您格式化前缀字符串。玩一点,我相信它会工作的
  • 更接近 - 它只需要省略那些不继续的管道。那是我特别坚持的一点。
【解决方案4】:

这是@Will(当前接受的)答案的变体。变化是:

  1. 这使用 Unicode 符号而不是 ASCII 以获得更令人愉悦的外观。
  2. 根元素不缩进。
  3. 组的最后一个子项在其后添加了一个“空白”行(便于直观解析)。

以伪代码的形式呈现,以便在 C++ 之外使用:

def printHierarchy( item, indent )
  kids = findChildren(item)  # get an iterable collection
  labl = label(item)         # the printed version of the item
  last = isLastSibling(item) # is this the last child of its parent?
  root = isRoot(item)        # is this the very first item in the tree?

  if root then
    print( labl )
  else
    # Unicode char U+2514 or U+251C followed by U+2574
    print( indent + (last ? '└╴' : '├╴') + labl )

    if last and isEmpty(kids) then
      # add a blank line after the last child
      print( indent ) 
    end

    # Space or U+2502 followed by space
    indent = indent + (last ? '  ' : '│ ')
  end

  foreach child in kids do
    printHierarchy( child, indent )
  end
end

printHierarchy( root, "" )

示例结果:

Body
├╴PaintBlack
├╴CarPaint
├╴Black_Material
├╴PaintBlue
├╴Logo
│ └╴Image
│
├╴Chrome
├╴Plastic
├╴Aluminum
│ └╴Image
│
└╴FabricDark

【讨论】:

    【解决方案5】:

    我正在使用以下方法打印 BST

    private void print(Node root, String prefix) {
        if (root == null) {
        System.out.println(prefix + "+- <null>");
        return;
        }
    
        System.out.println(prefix + "+- " + root);
        print(root.left, prefix + "|  ");
        print(root.right, prefix + "|  ");
    }
    

    以下是输出。

    +- 43(l:0, d:1)
    |  +- 32(l:1, d:3)
    |  |  +- 10(l:2, d:0)
    |  |  |  +- <null>
    |  |  |  +- <null>
    |  |  +- 40(l:2, d:2)
    |  |  |  +- <null>
    |  |  |  +- 41(l:3, d:0)
    |  |  |  |  +- <null>
    |  |  |  |  +- <null>
    |  +- 75(l:1, d:5)
    |  |  +- 60(l:2, d:1)
    |  |  |  +- <null>
    |  |  |  +- 73(l:3, d:0)
    |  |  |  |  +- <null>
    |  |  |  |  +- <null>
    |  |  +- 100(l:2, d:4)
    |  |  |  +- 80(l:3, d:3)
    |  |  |  |  +- 79(l:4, d:2)
    |  |  |  |  |  +- 78(l:5, d:1)
    |  |  |  |  |  |  +- 76(l:6, d:0)
    |  |  |  |  |  |  |  +- <null>
    |  |  |  |  |  |  |  +- <null>
    |  |  |  |  |  |  +- <null>
    |  |  |  |  |  +- <null>
    |  |  |  |  +- <null>
    |  |  |  +- <null>
    

    【讨论】:

    • 相当不错的@KSC,到目前为止对我来说是 BST 的最佳方法。我将省略 null 的输出。
    【解决方案6】:

    这是 Joshua Stachowski 答案的通用版本。 Joshua Stachowski 的回答的好处是它不需要实际的节点类来实现任何额外的方法,而且看起来也不错。

    我使他的解决方案通用,无需修改代码即可用于任何类型。

        public static void PrintTree<T>(T rootNode,
                                        Func<T, string> nodeLabel, 
                                        Func<T, List<T>> childernOf)
                {
                    var firstStack = new List<T>();
                    firstStack.Add(rootNode);
    
                    var childListStack = new List<List<T>>();
                    childListStack.Add(firstStack);
    
                    while (childListStack.Count > 0)
                    {
                        List<T> childStack = childListStack[childListStack.Count - 1];
    
                        if (childStack.Count == 0)
                        {
                            childListStack.RemoveAt(childListStack.Count - 1);
                        }
                        else
                        {
                            rootNode = childStack[0];
                            childStack.RemoveAt(0);
    
                            string indent = "";
                            for (int i = 0; i < childListStack.Count - 1; i++)
                            {
                                indent += (childListStack[i].Count > 0) ? "|  " : "   ";
                            }
    
                            Console.WriteLine(indent + "+- " + nodeLabel(rootNode));
                            var children = childernOf(rootNode);
                            if (children.Count > 0)
                            {
                                childListStack.Add(new List<T>(children));
                            }
                        }
                    }
                }
    

    用法

     PrintTree(rootNode, x => x.ToString(), x => x.Children);
    

    【讨论】:

      【解决方案7】:

      不使用递归的完全可选的最佳方式是` https://github.com/tigranv/Useful_Examples/tree/master/Directory%20Tree

      public static void DirectoryTree(string fullPath)
          {
          string[] directories = fullPath.Split('\\');
          string subPath = "";
          int cursorUp = 0;
          int cursorLeft = 0;
      
          for (int i = 0; i < directories.Length-1; i++)
          {
              subPath += directories[i] + @"\";
              DirectoryInfo directory = new DirectoryInfo(subPath);
              var files = directory.GetFiles().Where(f => !f.Attributes.HasFlag(FileAttributes.Hidden)).Select(f => f.Name).ToArray();
              var folders = directory.GetDirectories().Where(f => !f.Attributes.HasFlag(FileAttributes.Hidden)).Select(f => f.Name).ToArray();             
              int longestFolder = folders.Length != 0 ? (folders).Where(s => s.Length == folders.Max(m => m.Length)).First().Length:0;
              int longestFle = files.Length != 0? (files).Where(s => s.Length == files.Max(m => m.Length)).First().Length : 0;
              int longestName =3 + (longestFolder <= longestFle ? longestFle:longestFolder)<=25? (longestFolder <= longestFle ? longestFle : longestFolder) : 26;
              int j = 0;
      
              for (int k = 0; k < folders.Length; k++)
              {
                  folders[k] = folders[k].Length <= 25 ? folders[k] : (folders[k].Substring(0, 22) + "...");
      
                  if (folders[k] != directories[i + 1])
                  {
                      Console.SetCursorPosition(cursorLeft, cursorUp + j);
                      Console.WriteLine("+" + folders[k]);
                      j++;
                  }
                  else
                  {
                      if (i != directories.Length - 2)
                      {
                          Console.SetCursorPosition(cursorLeft, cursorUp + j);
                          Console.WriteLine("-" + folders[k] + new string('-', longestName - directories[i + 1].Length) + "--\u261B");
                          j++;
                      }
                      else
                      {
                          Console.ForegroundColor = ConsoleColor.Red;
                          Console.SetCursorPosition(cursorLeft, cursorUp + j);
                          Console.WriteLine("***"+ folders[k] + "***");
                          Console.ForegroundColor = ConsoleColor.Gray;
                          j++;
                      }
                  }
              }
      
              for(int k = 0; k <  files.Length; k++)
              {
                  files[k] = files[k].Length <= 25 ? files[k] : (files[k].Substring(0, 22) + "...");
                  Console.SetCursorPosition(cursorLeft, cursorUp + j);
                  Console.WriteLine("+" + files[k]);
                  j++;
              }
      
              cursorUp += Array.IndexOf(folders, directories[i+1]) + 1;
              cursorLeft += longestName+3;
          }
      }
      

      【讨论】:

        【解决方案8】:

        使用 (y, x) 坐标

        这里是C代码:

        void printVLine(wchar_t token, unsigned short height, unsigned short y, unsigned short x);
        const static wchar_t TREE_VLINE = L'┃';
        const static wchar_t TREE_INBRANCH[] = L"┣╾⟶ ";
        const static wchar_t TREE_OUTBRANCH[] = L"┗╾⟶ ";
        
        typedef void (*Printer)(void * whateverYouWant);
        const static unsigned int  INBRANCH_SIZE = sizeof(TREE_INBRANCH) / sizeof(TREE_INBRANCH[0]);
        const static unsigned int OUTBRANCH_SIZE = sizeof(TREE_OUTBRANCH) / sizeof(TREE_OUTBRANCH[0]);
        
        size_t Tree_printFancy(Tree * self, int y, int x, Printer print){
            if (self == NULL) return 0L;
            //
            size_t descendants = y;
            move(y, x);
            print(Tree_at(self));
            if (!Tree_isLeaf(self)){ // in order not to experience unsigned value overflow in while()
                move(++y, x); 
                size_t i = 0;
                while(i < Tree_childrenSize(self) - 1){
                    wprintf(TREE_INBRANCH);
                    size_t curChildren = Tree_printFancy(
                           Tree_childAt(self, i), y, x + INBRANCH_SIZE, print
                    );
                    printVLine(TREE_VLINE, curChildren , y + 1, x);
                    move((y += curChildren), x);
                    ++i;
                }
                wprintf(TREE_OUTBRANCH); 
                y += Tree_printFancy(       // printing outermost child
                    Tree_childAt(self, i), y, x + OUTBRANCH_SIZE, print
                ) - 1;
            }   
            return y - descendants + 1;
        }
        

        它适用于控制台打印。 函数 move(y, x) 将光标移动到屏幕上的 (y, x) 位置。 最好的部分是,您可以通过更改变量来更改输出样式 TREE_VLINE、TREE_INBRANCH、TREE_OUTBRANCH,最后两个字符串的长度无关紧要。你可以打印任何你喜欢的东西,通过传递打印机函数指针,它将打印当前树节点的值。 输出看起来像this

        【讨论】:

          猜你喜欢
          • 2016-08-14
          • 1970-01-01
          • 1970-01-01
          • 2012-10-17
          • 2021-08-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-07-14
          相关资源
          最近更新 更多