功能风格的最快排序

时间:2013-12-03 01:44:52

标签: sorting functional-programming

作为一名本科生,我研究了O(n log n)算法,用于排序和证明我们在一般情况下无法做得更好,我们只能比较2个数字。这是一种随机存取存储器计算模型。 我想知道功能样式(引用透明)程序是否存在这样的理论下界。让我们假设每次beta减少都算作一步,每次比较都算作一步。

2 个答案:

答案 0 :(得分:1)

因此,让我们假设您同意我们在一般情况下不能比(n * log n)做得更好的证明,其中我们唯一拥有的是比较。

所以问题是我们是否可以证明我们可以做到同样没有副作用。

这样做的一种方法是使用这样的想法:如果你可以在O(n * log n)中构建一个不可变的二进制搜索树,然后用中缀遍历它(可以在O(n)中完成),那么我们就会有排序算法。

如果我们可以遍历所有项目并将每个项目添加到O(log n)中的平衡不可变(持久)树中,它将为我们提供O(n * log n)算法。

我们可以在O(log n)中添加一个持久的二叉树吗?当然,在持久数据结构库的每个合理实现中都存在平衡二叉搜索树的不可变变体,并且O(log n)插入。

为了弄清楚为什么可能,想象一个标准的平衡二进制搜索树,例如,红黑树。您可以通过遵循与可变算法相同的算法来创建它的不可变版本,除非指针或颜色发生变化,您需要分配一个新节点,从而将所有父节点分配给根(同时在必要时同时转换它们) 。不改变的侧枝可以重复使用。最多有O(log n)个受影响的节点,因此每次插入最多只有O(log n)操作(包括分配)。如果您知道红黑,您可以看到除了常数之外没有其他乘数(对于旋转,您可以为受影响的兄弟提供一些额外的分配,但这仍然是一个常数因素。)

这非常非正式的演示可以让你知道O(n * log n)的排序证明,没有副作用。但是,还有一些我遗漏的东西。例如。这里的分配被认为是O(1),这可能并不总是如此,但那会太复杂。

答案 1 :(得分:0)

我认为现代函数式编程实现(至少是Clojure,因为那是我所知道的唯一一个)确实有不可变的内存,但这并不意味着改变列表会导致复制整个原始列表。因此, 我不相信在使用命令式或功能性习语实现排序算法之间存在数量级的计算差异。

更多关于如何在不进行内存复制的情况下修改不可变列表:

有关如何使用此示例的示例,请参阅Clojure参考中的以下代码段:http://clojure-doc.org/articles/tutorials/introduction.html

  

...在Clojure中,所有标量和核心数据结构都是这样的。他们   是价值观。他们是不变的。

     

地图{:name "John" :hit-points 200 :super-power :resourcefulness}是一个   值。如果你想“改变”John的命中点,你就不会改变   任何东西本身,但你只需要召唤一个全新的hashmap   值。

     

但等等:如果您已经完成了类似C的任何命令式样式编程   语言,这听起来很浪费。但是,阴到此   不变的阳光就是---幕后--- Clojure分享   数据结构。它会跟踪所有碎片并重新使用它们   普遍地。例如,如果您有1,000,000个项目列表并且想要   要再说一件事,你只要告诉Clojure,“给我一个新的   但是这个项目添加了“---和Clojure尽职尽责地给你回复了   1,000,001项目列表在任何时间平坦。不知道你重新使用它   原始清单。

为什么关于不变性的大惊小怪?

我不知道函数式编程的完整历史,但在我看来,函数式语言的不变性特征基本上抽象出共享内存的复杂性

虽然这很酷,但如果没有支持整个机制的语言管理共享数据结构,那么对于许多用例而言,这将是非常缓慢的。