【问题标题】:Doubly linked data structures in erlangerlang中的双重链接数据结构
【发布时间】:2012-12-17 08:34:22
【问题描述】:

您好,我想制作一棵树,在父子之间保持双向引用。但似乎不可能实现,因为当我创建第一个对象时,我没有另一个对象,因此无法引用它。这是一些示例代码。

-record(node,{name,children,root}).

main()->
    A = #node{name="node-A",
          children=[B], %variable B is  unbound
          root=nil},
    B = #node{name="node-B",
          children=[],
          root=A},
    Tree = A.

这个问题的另一个例子是实现一个双向链表 (http://en.wikipedia.org/wiki/Doubly_linked_list)

-record(node,{prev,name,next}).

main()->
    A = #node{prev=nil,
          name="node-A",
          next=B}, % variable B is unbound
    B = #node{prev=A,
          name="node-B",
          next=nil},
    LinkedList = A.

有没有办法实现这种结构。

【问题讨论】:

  • 为什么不在 -define 中定义你的结构,其中子项默认为未定义。然后在创建节点 B 后创建 NodeA = B#node{children=A}.... 抱歉,无法正常工作。希望我知道如何删除笔记
  • 为什么不尝试先创建孩子然后分配他们父母?或者创建父母并给他们默认的孩子,然后一旦孩子准备好,更新父母?

标签: tree erlang


【解决方案1】:

当您有“链接”(如指针)时,您可以创建双向链表。在erlang中你没有这样的链接,你甚至没有真正的变量,你不能改变它们。以下是循环列表的一些示例,但应谨慎实施:Can Circular Lists be defined in Erlang?

也许您可以告诉我们为什么需要双向链接树?也许erlang有更好的解决方案。

编辑:您可以使用digraph。您的树可以表示为循环图,其中您有从 A 到 B 和从 B 到 A 的顶点。具有根节点 A 和子节点 B 和 C 的树示例:

main()->
    Tree = digraph:new([cyclic]),
    A = digraph:add_vertex(Tree,"vertexA"),
    B = digraph:add_vertex(Tree,"vertexB"),
    C = digraph:add_vertex(Tree,"vertexC"),
    digraph:add_edge(Tree, A, B),
    digraph:add_edge(Tree, B, A),
    digraph:add_edge(Tree, A, C),
    digraph:add_edge(Tree, C, A),
    digraph:get_path(Tree, B, C).

结果:["vertexB","vertexA","vertexC"]

【讨论】:

  • 有没有办法跟踪/保持对链表末尾的引用以在 erlang 中进行附加操作 O(1)?
  • 不,您不能添加到列表末尾。但是你可以用我的例子来模拟这个。 Digraph 使用 ets,其插入成本为 O(1)。
【解决方案2】:

您可以检查它是如何在 ferd 的 zippers 库中实现的

【讨论】:

  • 哦,这很整洁。很高兴我找到了这个:)。
【解决方案3】:

你可以像这样定义一个记录

-record(my_node,{leftchild,rightchild,parent,value}.

并将你的树存储在 ets 表中,

ets:new(my_tree,[named_table, ordered_set, public]),
...

然后您可以使用表键作为“指针”来管理链接

Root = {make_ref(),#my_node{value=somevalue}}
ets:insert(my_tree,Root),
A_child = {make_ref(),#my_node{value=othervalue}},
addchild(Root,A_child,left),
...

addchild(Parent={Pref,Pval},Child={Cref,Cval},left) ->
    ets:insert(my_tree,{Pref,Pval#my_node{leftchild=Cref}}),
    ets:insert(my_tree,{Cref,Cval#my_node{parent=Pref}});
addchild(Parent={Pref,Pval},Child={Cref,Cval},right) ->
    ets:insert(my_tree,{Pref,Pval#my_node{rightchild=Cref}}),
    ets:insert(my_tree,{Cref,Cval#my_node{parent=Pref}}).

但也许您应该查看更多“erlang 风格”的数据表示来解决您的问题。我提出的解决方案还有一个问题,如果有多个进程访问树,因为树的更新不是原子的。在这种情况下,您应该使用 mnesia,它是 ets 之上的一个数据库层,它将为您带来原子事务。

【讨论】:

    【解决方案4】:

    不,没有直接的方法在 Erlang 中实现双向链表,因为所有数据都是不可变的。即使你可以设置它(你不能),你也不能任何事情,因为所有数据都是不可变的。此处介绍的其他解决方案向您展示了通过构建以双向链表方式运行的数据结构来解决此问题的方法。但不是。

    【讨论】:

      【解决方案5】:

      如果您真的需要做类似的事情,您可以使用某种 ID 来引用您的节点。例如

      A = #node{name="node-A",
            children=["node-B"],
            parent=nil},
      B = #node{name="node-B",
            children=[],
            parent="node-A"},
      NodeDict = dict:from_list([{A#node.name, A}, {B#node.name, B}]),
      Tree = #tree{root=A#node.name, nodes=NodeDict}.
      

      【讨论】:

        最近更新 更多