Ruby 以文本/ASCII格式呈现水平二叉ish树的算法

Ruby 以文本/ASCII格式呈现水平二叉ish树的算法,ruby,algorithm,language-agnostic,text,binary-tree,Ruby,Algorithm,Language Agnostic,Text,Binary Tree,这是一个非常普通的二叉树,除了一个节点可能是空的 我想找到一种以水平方式输出它的方法(也就是说,根节点位于左侧,并扩展到右侧) 我有一些垂直扩展树的经验(根节点在顶部,向下扩展),但我不确定从哪里开始,在这种情况下 它最好遵循以下几条规则: 如果节点只有一个子节点,则可以将其作为冗余节点跳过(始终显示没有子节点的“结束节点”) 相同深度的所有节点必须垂直对齐;所有节点必须位于所有较浅节点的右侧,而位于所有较深节点的左侧 节点具有包含其深度的字符串表示形式 每个“终端节点”都有自己独特的线路;也

这是一个非常普通的二叉树,除了一个节点可能是空的

我想找到一种以水平方式输出它的方法(也就是说,根节点位于左侧,并扩展到右侧)

我有一些垂直扩展树的经验(根节点在顶部,向下扩展),但我不确定从哪里开始,在这种情况下

它最好遵循以下几条规则:

  • 如果节点只有一个子节点,则可以将其作为冗余节点跳过(始终显示没有子节点的“结束节点”)
  • 相同深度的所有节点必须垂直对齐;所有节点必须位于所有较浅节点的右侧,而位于所有较深节点的左侧
  • 节点具有包含其深度的字符串表示形式
  • 每个“终端节点”都有自己独特的线路;也就是说,行数是树中的结束节点数,当一个结束节点位于一条线上时,该结束节点之后该线上可能没有其他内容
  • 作为最后一条规则的结果,根节点最好位于左上角或左下角;最好是左上角
例如,这是一个有效的树,有六个端点节点(节点由名称及其深度表示):编辑:请参阅问题的底部,以获得更简单的替代渲染方法。

[a0]-----------[b3]------[c5]------[d8] \ \ \----------[e9] \ \----[f5] \-[g1]--------[h4]------[i6] \ \--------------------[j10] \-[k3] 通过广度优先或深度优先搜索,生成这个网格应该很容易。也许最简单的方法是保留一个2D数组,将找到的每个重要节点放入其中,为每个“第二个子节点”插入一行

现在,了解这些事实:

  • 行中的最后一个节点必须是结束节点
  • 子节点始终位于其父节点的右侧、同一行或更低的位置
  • 所有非结束节点必须正好有两个子节点
  • 因此,所有非结束节点的子节点都位于其列右侧的第一个,第一个子节点位于同一行,第二个子节点位于其下方的n行,其中n是其右侧的节点数
我们可以看到,给定任何有效的网格,可以说,有一种明确的方式来“连接点”;有一个明确的树被表示

现在,“连接点”不再是一个二叉树结构问题……它只是一个装饰问题。我们只需要构建一个算法,将正确的
-
\
放在它们可以去的地方,也许只遵循简单的网格/词典规则,而不是二叉树结构规则

基本上,这意味着渲染树的问题现在比渲染网格的问题要简单得多,带有花哨的装饰

有人能提出制定这些规则的方法吗?或者完全不同的方法


编辑

我构思了一个非常简单的最终渲染:

--d0----d1----d3----d4----d5----d6----d8----d9----d10-- => guide line (not rendered) [a0 ]-------[b3 ]-------[c5 ]-------[d8 ] | | \---------------[e9 ] | \---------[f5 ] \---[g1 ]-------[h4 ]-------[i6 ] | \---------------------------[j10] \---[k3 ] --d0----d1----d3----d4----d5----d6----d8----d9----d10-- => guide line (not rendered)
[a0]-----------[b3]------[c5]------[d8]
    \              \         \----------[e9]
     \              \----[f5]
      \-[g1]--------[h4]------[i6]
            \           \--------------------[j10]
             \-[k3]
--d0---d1---d3---d4---d5---d6---d8---d9---d10---=>引导线(未渲染) [a0]----[b3]----[c5]----[d8] || \------[e9] |\-----------[f5] \---[g1]----[h4]----[i6] |\------------------------------[j10] \---[k3] --d0---d1---d3---d4---d5---d6---d8---d9---d10---=>引导线(未渲染)
尝试创建这个可能比我之前发布的更容易。首先,它保留了一个漂亮的网格形状,你不必对对角线变化无常。所有行都沿清晰可见的列线映射。不幸的是,它远没有第一个漂亮。

看起来是个有趣的问题;如果我有更多的时间,我很乐意试一试

我可能会采用以下方法:

  • 开始渲染“right”(或者在您的示例中为“top”)节点,直到到达终点。(即:呈现a、b、c和d)
  • 返回到具有子节点(即:c)的最后一个节点,并递归地执行相同的操作
  • 您必须保留一个全局变量,指示正在打印的行。每次递归调用都会增加这个变量

    编辑:好的,忍不住尝试编写一些未经测试的伪代码,希望它能工作:

    function print_tree(Node n) {
        print "\n" // begin on a fresh new line
        childs = new Array();
        do {
            if (n.hasLeftChild) {
                childs.push(n.leftChild)
            }
            print "---" + n.id    //this needs a lot of tweaking, but you get the idea
        } while(n = n.rightChild)
        childs.reverse()
        foreach(child in childs) {
            print_tree(child);
        }
    }
    

    如果不是对整个树进行搜索,您可能需要执行深度优先搜索,以便沿二维正确调整其输出大小

    如果有
    N个
    结束节点,则必须有
    N-1个
    内部节点和2个子节点。(可以有任意数量的内部节点和一个子节点,我们必须对其进行计数以获得深度,否则忽略)。因此,生成树相当于将这些节点定位在网格上,其中:

    • 网格中的行数为
      N
    • 我认为列的数量介于
      1+楼层(log2(N))
      2*N-1
      之间,这取决于重叠的程度;不过,对于我们的目的来说,这可能无关紧要
    • 每个端点显示在不同的行上
    • 相同深度的所有节点显示在同一列中
    • 所有内部节点与其最右边的子节点显示在同一行上
    那么,让我们看看:

    • 从右到左,先沿着树的深度走
    • 对于每个端点,记录其深度和标签
    • 对于每个2个子内部,记录其深度、标签以及最右侧和最左侧子端点的索引
    • 按深度对整个批次进行排序——这将为您提供列排序,不同深度的数量表示实际的列数。(我认为,所有其他排序都应该自动从walk中出来,但这里不是这样,因为任何分支都可以有任何深度。)
    • 将所有节点放置在网格中
    • 将每个非端点节点右侧的空单元格标记为水平分支
      function print_tree(Node n) {
          print "\n" // begin on a fresh new line
          childs = new Array();
          do {
              if (n.hasLeftChild) {
                  childs.push(n.leftChild)
              }
              print "---" + n.id    //this needs a lot of tweaking, but you get the idea
          } while(n = n.rightChild)
          childs.reverse()
          foreach(child in childs) {
              print_tree(child);
          }
      }
      
      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      
      namespace SO_ASCII_tree
      {
          class Program
          {
              static void Main()
              {
                  Node root = …;
      
                  StringBuilder[] lines = Enumerable.Range(0, root.Leaves).Select(i => new StringBuilder()).ToArray();
      
                  Node[] currentLevel = new Node[] { root };
                  int level = 0;
                  int min = 0;
                  int max = 0;
                  while (currentLevel.Any())
                  {
                      NamedNode[] namedNodes = currentLevel.OfType<NamedNode>().ToArray();
                      if (namedNodes.Any())
                      {
                          min = namedNodes.Select(node => lines[node.Line].Length).Max();
                          min = Math.Max(min, max);
                          if (min != 0)
                              min++;
                          foreach (NamedNode namedNode in namedNodes)
                              WriteAtPosition(lines[namedNode.Line], namedNode.Write(level), min, '-');
                          max = namedNodes.Select(node => lines[node.Line].Length).Max();
                          // change to max = min + 1; for long names
                      }
      
                      foreach (Node node in currentLevel)
                          node.SetChildLines();
      
                      Binary[] binaries = namedNodes.OfType<Binary>().ToArray();
                      foreach (Binary binary in binaries)
                          GoDown(lines, binary.Line, binary.Right.Line);
      
                      currentLevel = currentLevel.SelectMany(node => node.Children).ToArray();
                      level++;
                  }
      
                  foreach (StringBuilder line in lines)
                      Console.WriteLine(line.ToString());
              }
      
              static void WriteAtPosition(StringBuilder line, string message, int position, char prepend = ' ')
              {
                  if (line.Length > position)
                      throw new ArgumentException();
                  line.Append(prepend, position - line.Length);
                  line.Append(message);
              }
      
              static void GoDown(StringBuilder[] lines, int from, int to)
              {
                  int line = from + 1;
                  int position = lines[from].Length;
                  for (; line <= to; line++, position++)
                      WriteAtPosition(lines[line], "\\", position);
              }
          }
      
          abstract class Node
          {
              public int Line
              { get; set; }
      
              public abstract int Leaves
              { get; }
      
              public abstract IEnumerable<Node> Children
              { get; }
      
              public virtual void SetChildLines()
              { }
          }
      
          abstract class NamedNode : Node
          {
              public string Name
              { get; set; }
      
              public string Write(int level)
              {
                  return '[' + Name + level.ToString() + ']';
              }
          }
      
          class Binary : NamedNode
          {
              public Node Left
              { get; set; }
              public Node Right
              { get; set; }
      
              int? leaves;
              public override int Leaves
              {
                  get
                  {
                      if (leaves == null)
                          leaves = Left.Leaves + Right.Leaves;
                      return leaves.Value;
                  }
              }
      
              public override IEnumerable<Node> Children
              {
                  get
                  {
                      yield return Left;
                      yield return Right;
                  }
              }
      
              public override void SetChildLines()
              {
                  Left.Line = Line;
                  Right.Line = Line + Left.Leaves;
              }
          }
      
          class Unary : Node
          {
              public Node Child
              { get; set; }
      
              int? leaves;
              public override int Leaves
              {
                  get
                  {
                      if (leaves == null)
                          leaves = Child.Leaves;
                      return leaves.Value;
                  }
              }
      
              public override IEnumerable<Node> Children
              {
                  get
                  {
                      yield return Child;
                  }
              }
      
              public override void SetChildLines()
              {
                  Child.Line = Line;
              }
          }
      
          class Leaf : NamedNode
          {
              public override int Leaves
              {
                  get
                  {
                      return 1;
                  }
              }
      
              public override IEnumerable<Node> Children
              {
                  get
                  {
                      yield break;
                  }
              }
          }
      }
      
      [a0]-----------[b3]------[c5]------[d8]
          \              \         \----------[e9]
           \              \----[f5]
            \-[g1]--------[h4]------[i6]
                  \           \--------------------[j10]
                   \-[k3]
      
      [a0]---------[b3]-------[c5]------[d8] \ \ `----------[e9] \ `-----[f5] `[g1]--------[h4]------[i6] \ `--------------------[j10] `[k3]