假设你有一个扁平的表,存储一个有序的树层次结构:
Id Name ParentId Order
1 'Node 1' 0 10
2 'Node 1.1' 1 10
3 'Node 2' 0 20
4 'Node 1.1.1' 2 10
5 'Node 2.1' 3 10
6 'Node 1.2' 1 20
这是一个图表,我们有[id] Name。根节点0是虚构的。
[0] ROOT
/ \
[1] Node 1 [3] Node 2
/ \ \
[2] Node 1.1 [6] Node 1.2 [5] Node 2.1
/
[4] Node 1.1.1
您将使用什么极简的方法将其输出到HTML(或文本,就此而言),作为一个正确有序、正确缩进的树?
进一步假设您只有基本的数据结构(数组和hashmap),没有带有父/子引用的花哨对象,没有ORM,没有框架,只有您的两只手。该表表示为一个结果集,可以随机访问。
伪代码或简单的英语是可以的,这纯粹是一个概念问题。
附加问题:在RDBMS中是否存在从根本上更好的方法来存储这样的树结构?
编辑和添加
回答一位评论者(Mark Bessey)的问题:根节点是不必要的,因为无论如何它都不会显示。ParentId = 0是表示“这些是顶级”的惯例。Order列定义了具有相同父节点的节点如何排序。
我所说的“结果集”可以被描绘成一个hashmap数组(继续使用这个术语)。因为我的例子本来就应该在那里。有些答案是额外的,首先构建它,但这没关系。
树可以任意深。每个节点可以有N个子节点。不过,我脑子里并没有“数百万条”树。
不要把我选择的节点命名(“节点1.1.1”)误认为是可以依赖的。节点也可以被称为“Frank”或“Bob”,没有隐含的命名结构,这只是为了让它更具可读性。
我已经发布了我自己的解决方案,所以你们可以把它拆成碎片。
如果元素是按树顺序排列的,如你的例子所示,你可以使用以下Python示例:
delimiter = '.'
stack = []
for item in items:
while stack and not item.startswith(stack[-1]+delimiter):
print "</div>"
stack.pop()
print "<div>"
print item
stack.append(item)
这样做的目的是维护一个表示树中当前位置的堆栈。对于表中的每个元素,它弹出堆栈元素(关闭匹配的div),直到找到当前项的父元素。然后它输出该节点的开始并将其推入堆栈。
如果希望使用缩进而不是嵌套元素输出树,可以简单地跳过print语句来打印div,并在每个项之前打印等于堆栈大小的若干倍的空格。例如,在Python中:
print " " * len(stack)
您还可以轻松地使用此方法构造一组嵌套的列表或字典。
编辑:我从你的澄清中看到,这些名称并不是节点路径。这就提出了另一种方法:
idx = {}
idx[0] = []
for node in results:
child_list = []
idx[node.Id] = child_list
idx[node.ParentId].append((node, child_list))
这将构造一个元组数组树(!)。Idx[0]表示树的根。数组中的每个元素都是一个二元组,由节点本身及其所有子元素的列表组成。构造后,可以保留idx[0]并丢弃idx,除非您希望通过节点的ID访问节点。
要扩展Bill的SQL解决方案,基本上可以使用平面数组来实现相同的功能。此外,如果你的字符串都有相同的长度,你的最大子代数是已知的(比如在一个二叉树中),你可以使用一个单一的字符串(字符数组)。如果你有任意数量的孩子,事情就会变得复杂一些……我必须检查我的旧笔记,看看能做些什么。
然后,牺牲一点内存,特别是如果你的树是稀疏的和/或不平衡的,你可以,通过一些索引数学,通过存储你的树随机访问所有的字符串,宽度优先在数组中,就像这样(对于二叉树):
String[] nodeArray = [L0root, L1child1, L1child2, L2Child1, L2Child2, L2Child3, L2Child4] ...
你知道弦的长度,你知道
我现在在工作,所以不能花太多时间在上面,但有兴趣,我可以获取一些代码来做到这一点。
我们过去用它来搜索由DNA密码子组成的二叉树,一个构建树的过程,然后我们将其平铺以搜索文本模式,当找到时,尽管索引数学(从上面反向),我们将节点找回…非常快速和有效,我们的树很少有空节点,但我们可以在一瞬间搜索千兆字节的数据。