我只是看着Eric Lippert对immutable binary tree的简单实现,我对此有疑问。在显示实施后,Eric说明了
请注意另一个不错的功能 不可变的数据结构就是它 不可能偶然(或 故意!)创建一棵树 包含一个循环。
似乎Eric的实现的这个特征不是仅仅来自不变性,而是来自树的叶子构建的事实。这自然会阻止节点将其任何祖先作为子节点。似乎如果你在另一个方向上构建了树,你就会引入循环的可能性。
我是否正确思考,或者在这种情况下循环的不可能性是否来自于不变性?考虑到来源,我想知道我是否遗漏了一些东西。
编辑:经过多思考后,似乎从叶子构建可能是创建不可变树的唯一方法。我对吗?答案 0 :(得分:12)
如果你真的想要努力,你可以创建一个包含循环的树,它是不可变的。例如,您可以定义一个不可变的图类,然后说:
Graph g = Graph.Empty
.AddNode("A")
.AddNode("B")
.AddNode("C")
.AddEdge("A", "B")
.AddEdge("B", "C")
.AddEdge("C", "A");
嘿,你有一个带有“周期”的“树” - 因为你当然没有一棵树,你有一个有向图。
但是实际上使用传统的“左右子树”实现二叉树的数据类型,则无法制作循环树(模数当然是使用反射或不安全代码的偷偷摸摸的技巧。)< / p>
答案 1 :(得分:11)
如果你使用的是不可变数据结构,那么在严格(而不是懒惰)语言中,就不可能创建一个循环;因为您必须按某种顺序创建元素,并且一旦创建了元素,您就不能将其变异以指向稍后创建的元素。因此,如果您创建了节点 n ,然后创建了指向 n 的节点 m (可能是间接的),则您永远无法通过导致完成循环 n 指向 m ,因为您不允许改变 n ,也不允许 n 指向的任何内容。
是的,你是对的,你只能通过从叶子建立来创建一个不可变的树;如果从根开始,则必须在创建根时修改根以指向其子项。只有从叶子开始,并创建每个节点指向其子节点,才能从不可变节点构造树。
答案 2 :(得分:2)
当你说“从树叶中建立起来”时,我想你会包含这样一个事实,即构造函数会带孩子,但从不带父母。
似乎你是在建造树 另一个方向,你会介绍 循环的可能性。
不,因为那时你会有相反的约束:构造函数必须采用父类但不是子代。因此,在创建所有祖先之前,永远不能创建后代。因此,没有可能的循环。
在考虑了一点之后,它 似乎是从树叶建立起来的 可能是创建一个的唯一方法 不可变树。我是对的吗?
不......看看我对Brian和ergosys的评论。
对于许多应用程序,其子节点指向其父节点的树不是很有用。我承认了。如果您需要按照其层次结构确定的顺序遍历树,则向上指向的树会使其变硬。
但是对于其他应用程序,这种树正是我们想要的那种。例如,我们有一个文章数据库。每篇文章都可以有一个或多个翻译。每个翻译都可以翻译。我们将此数据结构创建为关系数据库表,其中每个记录都有一个指向其父级的“外键”(指针)。这些记录都不需要更改其指向其父级的指针。添加新文章或翻译时,将使用指向适当父级的指针创建记录。
一个常见的用例是查询翻译表,查找特定文章的翻译或特定语言的翻译。啊,你说,翻译表是一个可变的数据结构。
当然可以。但它与树分开。我们使用(不可变)树来记录层次关系,并使用可变表来迭代项目。在非数据库情况下,您可以使用指向树节点的哈希表。无论哪种方式,树本身(即节点)都不会被修改。
Here's another example of this data structure,包括如何有效地访问节点。
我的观点是OP的问题的答案是“是”,我同意你们其他人的观点,预测周期确实来自单独的不变性。虽然你可以在另一个方向(自上而下)构建一个树,如果你这样做,并且它是不可变的,它仍然不能有循环。
当你谈论强大的理论保证时,如
不可变数据结构的另一个不错的功能是 不可能意外(或 故意!)创建一棵树 包含一个循环 [强调原文]
“这样的树不会非常有用”相比之下相形见绌 - 即使它是真的。 人们总是偶然地创建无用的数据结构,更不用说故意创造所谓无用的数据结构了。假定的无用性并不能保护程序免受数据结构中循环的陷阱的影响。理论上的保证(假设你真的符合它所说的标准)。
P.S。向上指向树的一个很好的特性是,你可以保证树的定义的一个方面,即向下指向的树数据结构(如Eric Lippert的)不会:每个节点最多只有一个父节点。 (参见David's comment和我的回复。)
答案 3 :(得分:1)
您无法从根构建它,它需要您改变已添加的节点。