Ruby 以文本/ASCII格式呈现水平二叉ish树的算法
这是一个非常普通的二叉树,除了一个节点可能是空的 我想找到一种以水平方式输出它的方法(也就是说,根节点位于左侧,并扩展到右侧) 我有一些垂直扩展树的经验(根节点在顶部,向下扩展),但我不确定从哪里开始,在这种情况下 它最好遵循以下几条规则:Ruby 以文本/ASCII格式呈现水平二叉ish树的算法,ruby,algorithm,language-agnostic,text,binary-tree,Ruby,Algorithm,Language Agnostic,Text,Binary Tree,这是一个非常普通的二叉树,除了一个节点可能是空的 我想找到一种以水平方式输出它的方法(也就是说,根节点位于左侧,并扩展到右侧) 我有一些垂直扩展树的经验(根节点在顶部,向下扩展),但我不确定从哪里开始,在这种情况下 它最好遵循以下几条规则: 如果节点只有一个子节点,则可以将其作为冗余节点跳过(始终显示没有子节点的“结束节点”) 相同深度的所有节点必须垂直对齐;所有节点必须位于所有较浅节点的右侧,而位于所有较深节点的左侧 节点具有包含其深度的字符串表示形式 每个“终端节点”都有自己独特的线路;也
- 如果节点只有一个子节点,则可以将其作为冗余节点跳过(始终显示没有子节点的“结束节点”)
- 相同深度的所有节点必须垂直对齐;所有节点必须位于所有较浅节点的右侧,而位于所有较深节点的左侧
- 节点具有包含其深度的字符串表示形式
- 每个“终端节点”都有自己独特的线路;也就是说,行数是树中的结束节点数,当一个结束节点位于一条线上时,该结束节点之后该线上可能没有其他内容
- 作为最后一条规则的结果,根节点最好位于左上角或左下角;最好是左上角
- 行中的最后一个节点必须是结束节点
- 子节点始终位于其父节点的右侧、同一行或更低的位置
- 所有非结束节点必须正好有两个子节点
- 因此,所有非结束节点的子节点都位于其列右侧的第一个,第一个子节点位于同一行,第二个子节点位于其下方的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---=>引导线(未渲染)
尝试创建这个可能比我之前发布的更容易。首先,它保留了一个漂亮的网格形状,你不必对对角线变化无常。所有行都沿清晰可见的列线映射。不幸的是,它远没有第一个漂亮。看起来是个有趣的问题;如果我有更多的时间,我很乐意试一试 我可能会采用以下方法:
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]