【发布时间】:2021-09-18 09:20:46
【问题描述】:
背景
我在业余时间处理Ullmans Elements of ML programming。最终目标是自学Andrew Appels Modern Compiler Implementation in ML。
在 Elements of ML 中,Ullman 描述了差异列表:
LISP 程序员知道一个技巧是差异列表,其中一个 通过保留作为您的额外参数来更有效地操作列表 函数,一个以某种方式代表你已经完成的事情的列表。 这个想法出现在许多不同的应用程序中;
Ullman 使用reverse 作为差异列表技术的示例。这是一个运行时间为 O(n^2) 的慢速函数。
fun reverse nil = nil
| reverse (x::xs) = reverse(xs) @ [x]
使用差异列表的速度更快
fun rev1(nil, M) = M
| rev1(x::xs, ys) = rev1(xs, x::ys)
fun reverse L = rev1(L, nil)
我的问题
我有这种二叉搜索树 (BST) 数据类型。
datatype 'a btree = Empty
| Node of 'a * 'a btree * 'a btree
在预购中收集元素列表的简单解决方案是
fun preOrder Empty = nil
| preOrder (Node(x, left, right)) = [x] @ preOrder left @ preOrder right
但 Ullman 指出 @ 运算符很慢,并在练习 6.3.5 中建议我使用差异列表来实现 preOrder。
经过一番摸索,我想出了这个功能:
fun preOrder tree = let
fun pre (Empty, L) = L
| pre (Node(x, left, right), L) = let
val L = pre(right, L)
val L = pre(left, L)
in
x::L
end
in
pre (tree, nil)
end
它预先输出元素。 但是它会在后序中评估树!而且代码比天真的preOrder 更丑。
> val t = Node(5,
Node(3,
Node(1, Empty, Empty),
Node(4, Empty, Empty)),
Node(9, Empty, Empty))
> preOrder t
val it = [5,3,1,4,9] : int list
现有艺术
我尝试在 ML 编程中搜索对差异列表的引用,发现 John Hughes original article 描述了如何使用差异列表进行反向操作。
我还在 Haskell 中找到了 Matthew Brecknells difference list blog post 的示例。他区分了使用累加器(如 Ullmans 反向示例)和为差异列表创建新类型。他还展示了一种树木压平机。但是我很难理解 Haskell 代码,并且希望在标准 ML 中进行类似的公开。 abc
问题
-
如何实现一个函数来实际评估预先排序的树并预先收集元素?遍历后是否必须反转列表?还是有什么其他技巧?
-
如何推广这种技术以适用于中序和后序遍历?
-
在 BST 算法中使用差异列表的惯用方式是什么?
【问题讨论】:
-
@ggorlen 是 stackoverflow 重写了亚马逊链接以使用他们的亚马逊帐户。
-
两个函数产生相同的结果,你的例子是预购。 (您可以将
pre缩减为x::pre(left, pre(right, L)),这与第一个版本非常相似。)第二个版本的一个实际问题是它不是尾递归的。 -
它确实与数学定义紧密映射:前序列表是节点值,然后是左孩子的前序遍历,然后是右孩子的前序遍历。此外,如果您想通过使用循环来避免(最有可能的)其他语言中的非尾递归,您需要维护自己的评估堆栈,这远非优雅。
-
相关:preorder。相关:tailrecursion-modulo-cons。 related(查找“地鼠”)。一个非常明确的 Prolog 答案是 this one。但它使用了突变——或者更确切地说,明确设置一个逻辑变量(我们只允许对一个未实例化的变量进行一次)。 这种技术在 Haskell 中通过 “打结” 进行了模拟。现在,一个人真的把一个人的心结成一个结!部分应用的附加运算符的功能组合是 easy (相比之下)。 :)
-
顺便说一句,为清楚起见,变量确实应该编号,即使使用相同的名称,
L,您的语言也允许:fun pre (Empty, L) = L| pre (Node(x, left, right), L1) = letval L2 = pre(right, L1)@ 987654344@in x::L3 end让数据流不言而喻。现在它也不丑了。 :) 顺便说一句,在 Haskell 中,由于其惰性求值,这段确切的代码将从左到右遍历树!
标签: recursion functional-programming binary-search-tree sml difference-lists