Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/9.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 如何在Data.Set中插入O(log(n))?_Haskell_Complexity Theory_Referential Transparency - Fatal编程技术网

Haskell 如何在Data.Set中插入O(log(n))?

Haskell 如何在Data.Set中插入O(log(n))?,haskell,complexity-theory,referential-transparency,Haskell,Complexity Theory,Referential Transparency,在查看Data.Set的文档时,我看到了这一点。然而,我直觉上认为它应该是O(n*log(n))(或者可能是O(n)),因为引用透明性要求在O(n)中创建前一棵树的完整副本 我理解,例如(:)可以设置为O(1)而不是O(n),因为这里不必复制完整列表;编译器可以将新列表优化为第一个元素加上指向旧列表的指针(请注意,这是一个编译器,而不是语言级别的优化)。然而,在Data.Set中插入一个值涉及到重新平衡,这在我看来相当复杂,以至于我怀疑是否有类似于列表优化的东西。我试着阅读,但无法用它回答我的问

在查看
Data.Set
的文档时,我看到了这一点。然而,我直觉上认为它应该是O(n*log(n))(或者可能是O(n)),因为引用透明性要求在O(n)中创建前一棵树的完整副本

我理解,例如
(:)
可以设置为O(1)而不是O(n),因为这里不必复制完整列表;编译器可以将新列表优化为第一个元素加上指向旧列表的指针(请注意,这是一个编译器,而不是语言级别的优化)。然而,在
Data.Set
中插入一个值涉及到重新平衡,这在我看来相当复杂,以至于我怀疑是否有类似于列表优化的东西。我试着阅读,但无法用它回答我的问题


那么:在(纯)函数式语言中,如何将一个元素插入到二叉树中是O(log(n))。在内部,元素存储在树中,这意味着您只需要沿插入路径创建新节点。未接触的节点可以在
集合的插入前和插入后版本之间共享。正如所指出的,在平衡树中,
O(log(n))
是插入路径的长度。(很抱歉忽略了这一重要事实。)

假设您的
类型如下所示:

data Tree a = Node a (Tree a) (Tree a)
            | Leaf
let t' = Node 10 tl (Node 15 (Node 12 Leaf Leaf) tr')
。。。假设你有一个类似这样的

let t = Node 10 tl (Node 15 Leaf tr')
。。。其中
tl
tr'
是一些命名子树。现在假设您要将
12
插入此树。好吧,看起来是这样的:

data Tree a = Node a (Tree a) (Tree a)
            | Leaf
let t' = Node 10 tl (Node 15 (Node 12 Leaf Leaf) tr')
子树
tl
tr'
t
t'
之间共享,您只需构造3个新的
节点即可,即使
t
的大小可能远大于3


编辑:重新平衡

关于再平衡,像这样思考,注意我在这里没有要求严格。假设你有一棵空树。已经平衡了!现在假设插入一个元素。已经平衡了!现在假设插入另一个元素。嗯,有一个奇数,所以你在那里做不了什么

这是棘手的部分。假设您插入了另一个元素。这可能有两种方式:向左或向右;平衡的或不平衡的。在不平衡的情况下,您可以清楚地执行树的旋转来平衡它。在平衡的情况下,已经平衡了

这里需要注意的是,你在不断地重新平衡。这并不是说你把一棵树弄得一团糟,决定插入一个元素,但是在你这样做之前,你需要重新平衡,然后在完成插入后留下一片混乱


现在假设您继续插入元素。树会变得不平衡,但不会太多。当这种情况发生时,首先你会立即纠正,其次,纠正会沿着插入路径发生,在平衡树中是
O(log(n))
。链接到的图纸中的旋转最多接触树中的三个节点以执行旋转。因此,在重新平衡时,您正在执行
O(3*log(n))
工作。这仍然是
O(log(n))
为了更加强调dave4420在评论中所说的内容,使
(:)
以恒定时间运行不涉及编译器优化。您可以实现自己的列表数据类型,并在一个简单的非优化Haskell解释器中运行它,它仍然是O(1)

列表被定义为初始元素加上列表(或者在基本情况下为空)。这里有一个与本机列表等效的定义:

data List a = Nil | Cons a (List a)
因此,如果您有一个元素和一个列表,并且您想用
Cons
从它们中构建一个新的列表,那么这只是直接从构造函数所需的参数创建一个新的数据结构。甚至不需要检查尾部列表(更不用说复制了),就像在执行类似于
Person“Fred”
的操作时检查或复制字符串一样

当您声称这是一个编译器优化而不是语言级优化时,您就大错特错了。此行为直接源自列表数据类型的语言级别定义

类似地,对于定义为项目加两棵树(或空树)的树,当您将项目插入非空树时,它必须位于左子树或右子树中。您需要构造包含元素的树的新版本,这意味着您需要构造包含新子树的新父节点。但另一个子树根本不需要遍历;它可以按原样放入新的父树中。在平衡树中,这是树的一半,可以共享


递归地应用这种推理应该会告诉您,实际上根本不需要复制数据元素;在下至插入元素最终位置的路径上只需要新的父节点。每个新节点存储3个内容:一个项(与原始树中的项引用直接共享)、一个未更改的子树(与原始树直接共享)和一个新创建的子树(与原始树共享其几乎所有结构)。平衡树中会有O(log(n))个节点。

它不需要复制整个树-只需要将节点从根复制到插入节点的位置。平衡二叉树中会有日志(n)。平衡函数,作为参考,在这里:–注意上面列出的文档有一个更简单的版本。
(:)
定义为O(1)——尽管名字很有趣,但它只是一个简单的构造函数。插入路径的长度为O(logn)——所以O(logn)为了更详细地分析自平衡二进制搜索树如何在O中插入和重新平衡