【问题标题】:how to implement doubly linked lists如何实现双向链表
【发布时间】:2012-05-10 07:49:09
【问题描述】:

在 Haskell 中是否可以有一个双向链表,实现它们的理想解决方案是什么?我正在实现一个场景图,其中每个小部件都有一个父级和一个子级,向上和向下查看图形是有益的。

【问题讨论】:

  • 对不起,我很迂腐,但是恕我直言,在纯 FP 的上下文中将 haskell 的列表称为“链表”或谈论“双向链表”会拖累很多必要的包袱(指针、破坏性更新)进入讨论并混淆事物。
  • 如果不直接与您的问题相关,则与您的目标相关...在论文“Huge Data but Small Programs”eprints.whiterose.ac.uk/5000 中实现了一个场景图-尽管该代码仍有望公开它从来没有被放在 Hackage 上。遗憾的是,似乎没有多少已发表的关于函数式编程领域的工作,这是一个很好的话题,这很可惜。
  • 与使用双向链表相比,在haskell 中将数据存储为单链树然后用zipper 遍历它要容易得多

标签: haskell doubly-linked-list


【解决方案1】:

双向链表不是数据类型,而是实现细节。我认为您想要的是一个类似列表的数据结构,您可以在其中高效地左右移动。这个数据结构是一个列表的拉链,只是一对列表。前缀以相反的顺序表示。要向左/向右移动,您只需将后缀列表的头部移动到前缀上,反之亦然。

【讨论】:

  • 这并不完全正确。双向链表可以形成一个 FIFO 队列,队列/出队的 O(1),但是每次在队列和出队之间交换时,使用 Zipper 做到这一点将是 O(n)。您无法满足双向链表的大 O 要求,其中任何东西实际上类似于(非 IO)Haskell 中的双向链表,并且除了一个您无法扩展的单个冻结列表之外的任何东西。 Sequence 提供了正确的操作,但它确实是一棵聪明的树,而不是列表。您可以使用 DLList 之类的 diff-list 使其以后可扩展,但这也没有合适的大 O。
【解决方案2】:

由于您(通常)在 Haskell 中没有 OO 样式的对象,因此认为数据具有自我意识是很奇怪的。请务必注意,您通常不会在 Haskell 数据类型中使用聚合,而是倾向于组合。

您可能想查看XMonad,看看他们的设计是否符合您的需求(代码的可读性令人惊讶)。

您可能还想重新构建您的设计,以便您永远不需要俯视您(例如,通过传递子“回调”)。

您可能还想看看是否可以为整个图表编写一个拉链。

【讨论】:

    【解决方案3】:

    在 Haskell 中创建一个双向链表并不实际,因为您必须一次构建它。

    例如,假设您有一个列表[1, 2, 3, 4, 5],您希望将其进行双向链接。现在,让我们想象一下列表是如何表示的:

    data DoubleList a
      = LeftEnd  a (DoubleList a)
      | Middle   a (DoubleList a) (DoubleList a)
      | RightEnd a (DoubleList a)
    

    (为了简单起见,我在两端使用了两个不同的构造函数)

    要构建上面的列表,你必须首先构建第一个元素:

    let e1 = LeftEnd  1 ...
    

    但是要构造第一个元素,你已经需要第二个元素了:

    let e1 = LeftEnd  1 e2
        e2 = Middle   2 e1 ...
    

    对于第二个元素,你需要第三个,等等:

    let e1 = LeftEnd  1 e2
        e2 = Middle   2 e1 e3
        e3 = Middle   3 e2 e4
        e4 = Middle   4 e3 e5
        e5 = RightEnd 5 e4
    

    由于惰性求值,这在 Haskell 中是可能的;这种策略被称为“打结”(而且您不必将其全部放在一个 let 块中;您可以将构造划分为函数)

    但是,换句话说,要创建一个双向链表,你需要一次构建它,如果你想改变它的任何部分,你要么需要使用Zipper,要么只做每次都有一份完整的副本。

    我建议改用Data.Sequence,这是一种优化的基于手指树的顺序存储实现。它支持非常快速的插入、删除和迭代,同时仍然是一个纯粹的函数式数据结构。

    否则,您可能只想使用 Zippers,但将它们用于树而不是列表。有关 Zippers 的更多信息,请访问Haskell Wiki。拉链非常适合这种情况,因为它们提供了您所追求的确切功能:如果您使用拉链访问一棵树,您可以访问您正在访问的树部分的“父母”,但树本身不必包含父引用。

    【讨论】:

    • 至于从list([a])构建双向链表,here的一码sn-p;这是create 函数。
    • Data.Sequence 的不错!现在有一个有趣的类型!还可以快速访问列表的两端,这正是我所追求的
    • “在 Haskell 中创建一个双向链表是不切实际的,因为你必须一次构建它。”多么具有误导性的开场白;它是haskell,所有东西都是一次性构建的或构建为流。
    • @Dmitry 不,可以通过 O(1) (:)(缺点)逐个元素地构造单链表。
    猜你喜欢
    • 2021-11-18
    • 2011-06-03
    • 2018-03-14
    • 2019-11-23
    • 1970-01-01
    • 2020-02-09
    • 2015-05-16
    • 2013-02-22
    相关资源
    最近更新 更多