在 ocaml 中定义树 + 指向其子树的指针

Posted

技术标签:

【中文标题】在 ocaml 中定义树 + 指向其子树的指针【英文标题】:Defining a tree + a pointer to its subtree in ocaml 【发布时间】:2012-03-06 05:03:52 【问题描述】:

如何在 OCaml 中定义一棵树 + 指向其子树的指针,以便在该子树中添加叶子需要恒定时间?

【问题讨论】:

在 OCaml 的纯子集中,数据是不可变的。所以你不能给树添加叶子。你只能制作一棵与老树相似但有额外叶子的新树。如果您使用 OCaml 的不纯结构,则该解决方案看起来与任何命令式语言几乎一样。您能展示一下到目前为止您尝试过的代码吗? 在 OCaml 的不纯子集中,数据是可变的。你可以改变记录、字符串、记录,所以你正在寻找的东西在 OCaml 中确实是可能的,而不是在纯语言中。 @Jeffrey:我所做的只是通过沿着路径添加叶子,所以就像type tree = Empty | Node of 'a * tree list let rec add a path = function | Empty -> Node (a, []) | Node(s, c) -> if (empty path) then Node(s, Node(a, [])::c) else Node(s, List.map (fun x -> if (greater_than path (path_to x)) then add a (substract path (path_to x)) else x) c) 但是,我使用的路径将在下一步构建树,所以有一个指针会很方便。也许我会在这里尝试一些建议,拉链或可变记录。 平衡树有帮助吗?如果节点被添加到所有地方并且上下文必须在整个树中移动,则拉链不会有很大的复杂性。 不,我不能使用平衡树,我需要保留树的结构。但是是的,我可以使用拉链。我想知道在一般情况下如何扩展它,因为每个节点可能有不同数量的后继者,我使用列表来表示子集 【参考方案1】:

如果您想使用纯函数表示,nlucaroni 建议的 zippers 确实是表示数据结构深处的光标的好解决方案,可以移动或用于更新结构。

如果您希望使用就地突变的解决方案,您可以通过 mutable 记录字段或从它派生的引用 (ref) 使用可变数据。例如:

type 'a tree_cell = mutable node : 'a tree
and 'a tree = Leaf of 'a | Branch of 'a tree_cell * 'a * 'a tree_cell

如果您持有'a tree_cell,您可以对其进行变异(在恒定时间内)。

let head node = (Leaf x | Branch(_, x, _)) = x

let duplicate cell =
  cell.node <- Branch (cell, head cell, node = cell.node)

编辑:在您问题的 cmets 中,您似乎表明您对 n 叉树的解决方案感兴趣。

一般的n元情况可以表示为

type 'a tree_cell = mutable node: 'a tree
and 'a tree = Branch of 'a * 'a tree_cell list

虽然拉链解决方案看起来像(未经测试的代码)

type 'a tree = Branch of 'a * 'a forest
and 'a forest = 'a tree list

(* the data between the current cursor and the root of the tree *)
type 'a top_context = Top | Under of 'a * 'a tree * 'a top_context

(* a cursor on the 'data' element of a tree *)
type 'a data_cursor = top_context * 'a tree list

(* plug some data in the hole and get a tree back *)
val fill_data : 'a data_cursor -> 'a -> 'a tree

(* a cursor on one of the children of a tree *)
type 'a list_zipper = 'a list * 'a list
type 'a children_cursor = top_context * 'a * 'a tree list_zipper

(* plug some subtree in the hole and get a tree back *)
val fill_children : 'a children_cursor -> 'a tree -> 'a tree

(* carve a data hole at the root; also return what was in the hole *)
val top_data : 'a tree -> 'a data_cursor * 'a

(* fill a data hole and get a cursor for the first children in return
   -- if it exists *)
val move_down : 'a data_cursor -> 'a -> ('a children_cursor * 'a tree) option
(* fill the children hole and carve out the one on the left *)
val move_left : 'a data_cursor -> 'a tree -> ('a data_cursor * 'a tree) option
val move_right : 'a data_cursor -> 'a tree -> ('a data_cursor * 'a tree) option
(* fill the children hole and get a cursor on the data *)
val move_up : 'a children_cursor -> 'a tree -> 'a data_cursor * 'a

【讨论】:

【参考方案2】:

二叉树的另一种(更简单和更通用的)解决方案:

type 'a t = 
  value : 'a;
  mutable left : 'a t option;
  mutable right : 'a t option;

使用这种类型,您可以根据需要独立设置左右子树。

【讨论】:

以上是关于在 ocaml 中定义树 + 指向其子树的指针的主要内容,如果未能解决你的问题,请参考以下文章

7. B+树

基础数据结构-二叉树-赫夫曼树的解码(详解)

二叉树的定义

B*树

B*树的定义

数据结构(十七)树的定义与存储结构