【问题标题】:Functional O(1) append and O(n) iteration from first element list data structure来自第一个元素列表数据结构的功能 O(1) 追加和 O(n) 迭代
【发布时间】:2011-03-16 11:31:21
【问题描述】:

我正在寻找一种支持以下操作的函数式数据结构:

  • 追加,O(1)
  • 按顺序迭代,O(n)

普通的函数链表只支持 O(n) 追加,而我可以使用普通的 LL 然后反转它,反向操作也将是 O(n),它(部分)否定了 O(1) 的 cons 操作.

【问题讨论】:

  • 您看过 System.Collections.Generic.LinkedList 吗?
  • 是的,但它不是一成不变的,这是一个要求。
  • 功能队列怎么样? randomhacks.net/articles/2007/02/08/…。起初这可能不会削减它,但它可能是一个开始。保留一个堆栈,然后反转一个副本以按顺序迭代将是一个额外的 O(n) 操作,但这仍然会使迭代 O(n)。

标签: data-structures f# functional-programming


【解决方案1】:

您可以使用 John Hughes 的恒定时间附加列表,现在似乎称为 DList。表示是一个从列表到列表的函数:空列表是恒等函数; append 是组合,而单例是 cons(部分应用)。在这种表示中,每个枚举都会花费您n 分配,因此可能不太好。

另一种方法是制作与数据结构相同的代数:

type 'a seq = Empty | Single of 'a | Append of 'a seq * 'a seq

枚举是一种树遍历,它要么会消耗一些堆栈空间,要么需要某种拉链表示。这是一个转换为列表但使用堆栈空间的树遍历:

let to_list t =
  let rec walk t xs = match t with
  | Empty -> xs
  | Single x -> x :: xs
  | Append (t1, t2) -> walk t1 (walk t2 xs) in
  walk t []

这里是一样的,但是使用了常量栈空间:

let to_list' t =
  let rec walk lefts t xs = match t with
  | Empty -> finish lefts xs
  | Single x -> finish lefts (x :: xs)
  | Append (t1, t2) -> walk (t1 :: lefts) t2 xs
  and finish lefts xs = match lefts with
  | [] -> xs
  | t::ts -> walk ts t xs in
  walk [] t []

您可以编写一个折叠函数来访问相同的元素,但实际上并不具体化列表;只需将 cons 和 nil 替换为更通用的内容即可:

val fold : ('a * 'b -> 'b) -> 'b -> 'a seq -> 'b

let fold f z t =
  let rec walk lefts t xs = match t with
  | Empty -> finish lefts xs
  | Single x -> finish lefts (f (x, xs))
  | Append (t1, t2) -> walk (t1 :: lefts) t2 xs
  and finish lefts xs = match lefts with
  | [] -> xs
  | t::ts -> walk ts t xs in
  walk [] t z

这是您的线性时间、常量堆栈枚举。玩得开心!

【讨论】:

  • 请注意,seq 已被用作 IEnumerable 的 F# 名称,并且使用 Seq.append 很诱人,但这会导致堆栈溢出。
【解决方案2】:

我相信你可以只使用标准的函数链表:

  • 要追加元素,可以使用cons(即O(1))
  • 要按插入顺序迭代元素,您可以先反转列表,
    (也就是O(N))然后遍历它,也就是O(N)(而2xO(N)依然只是O(N))。

【讨论】:

  • 追加是对列表的操作,而不是元素和列表。 append 的代价是左参数的长度。
【解决方案3】:

差异列表怎么样?

type 'a DList = DList of ('a list -> 'a list)

module DList =
  let append (DList f) (DList g) = (DList (f << g))
  let cons x (DList f) = (DList (fun l -> x::(f l)))
  let snoc (DList f) x = (DList (fun l -> f(x::l)))
  let empty = DList id
  let ofList = List.fold snoc empty
  let toList (DList f) = f []

【讨论】:

  • 我讨厌人们使用这种数据结构而不给 John Hughes 提供信用。
【解决方案4】:

您可以创建一个函数式双端队列,它提供O(1) 添加到任一端,并提供O(N) 用于任一方向的迭代。 Eric Lippert 写了关于不可变双端队列的一个有趣版本on his blog 请注意,如果您环顾四周,您会发现该系列的其他部分,但这是对最终产品的解释。另请注意,通过一些调整,可以对其进行修改以利用 F# 区分联合和模式匹配(尽管这取决于您)。

这个版本的另一个有趣的属性,O(1) 从任一端查看、删除和添加(即 dequeueLeft、dequeueRight、dequeueLeft、dequeueRight 等)仍然是 O(N),而 O(N*N)双列表方法)。

【讨论】:

    【解决方案5】:

    circularly-linked list 呢?它支持 O(1) 次追加和 O(n) 次迭代。

    【讨论】:

      猜你喜欢
      • 2011-06-17
      • 2011-08-26
      • 2012-05-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多