clojure不可变二叉搜索树插入

clojure不可变二叉搜索树插入,clojure,binary-search-tree,Clojure,Binary Search Tree,我目前正试图在clojure中实现一个不可变的BST。 这是我的生成树功能: (定义生成树[v]{:v:l nil:r nil}) 和插入: (defn insert [tree v] (if (nil? tree) (make-tree v) (case (compare v (tree :v)) -1 (assoc tree :l (insert (:l tree) v)) 0 tree 1 (assoc tree :r (insert

我目前正试图在clojure中实现一个不可变的BST。 这是我的生成树功能:

(定义生成树[v]{:v:l nil:r nil})

和插入:

(defn insert [tree v]
  (if (nil? tree)
    (make-tree v)
    (case (compare v (tree :v))
      -1 (assoc tree :l (insert (:l tree) v))
      0 tree
      1 (assoc tree :r (insert (:r tree) v)))))
问题是,这个insert函数将溢出如下内容

(减少插入(生成树1)(范围10000))

我知道我可以平衡这棵树,这样我就不需要超过1000个深度。我仍然很好奇是否有任何方法来定义函数,这样它就不会溢出

由于可变版本只修改节点,因此不需要存储父节点,这似乎很方便


在现实生活中你会选择什么?可变还是不可变?

您的实现不是自平衡的,您的示例是按顺序插入元素的最坏情况。堆栈溢出是由于深度递归造成的。为了避免这种情况,您需要以延续传递样式重写算法,以便可以使用尾部递归或创建显式展开堆栈,而不是隐式使用调用堆栈

在现实生活中,Clojure已经有了一个不可变的自平衡树,
排序集
排序映射
,我在大多数情况下都会使用它。Java有可变的
TreeMap
,如果需要,您可以通过Clojure的Java互操作轻松地利用它


(进入(排序集)(范围10000))
要添加到A.韦伯的答案中:

虽然纯功能性实现需要在CPS中编写,但您可以将树实现为功能包装器+可变节点,其中修改将按如下方式进行:

  • 包装器创建一个新的空根,并告诉当前根执行修改,并将结果放入新根中

  • 如果新根确定要修改自己的值,它会将其子级复制到新根,将新值放入新根并返回

  • 否则,它会将其值和不需要修改的分支复制到新根目录中,并在新根目录中安装一个新的(空白)节点,以代替需要修改(或者可能需要修改)的分支

  • 当前根通知需要修改的分支修改自身,并将新的空白节点交给它

  • 分支在重复步骤2-5中充当根

  • 包装器使用新根创建一个新的包装器。新包装器作为顶级操作的结果返回

  • 上面的第2-5点描述了一个递归过程,但它实际上是尾部递归的,因此可以重写为循环

    在所有这些都完成之后,旧的包装器当然是非常好的,并且仍然保持相同的树(因为所有的突变只涉及新节点)

    事实上,许多Clojure数据结构始终使用包含的可变性(主要涉及数组)。(虽然不是树形图;使用可变性的实际模式也不同,但使用包含的可变性在内部加快速度,同时在外部维护功能接口的基本前提是相似的。)


    我还将添加两条相切的评论:

    首先,您的实现假设
    compare
    将返回-1、0、1中的一个,实际上,它可以自由返回任何负数表示“小于”,任何正数表示“大于”(
    (compare“foo”“bar”)
    在我的REPL中计算为
    4
    ,很可能是任何JVM REPL,尽管确切的值同样未指定)

    其次,如果您对Clojure中实现的自平衡树的实际示例感兴趣,您可能想看看,这是Clojure中的
    PersistentTreeMap
    PersistentTreeSet
    的实现。它基于ClojureScript实现,而ClojureScript又是Clojure Java实现的一个端口。在这一点上,我想说它纯粹是实验性的,但它似乎确实有效(通过了CLJS测试套件,达到了我在REPL上预期的效果),并且可以作为Clojure中简单PDS实现的示例



    1基本上,在编写ClojureScript实现之后,我想看看生成相同数据结构的Clojure实现需要多少额外工作。很明显会有一些额外的工作,因为有Java接口要实现,一些数组处理代码需要调整等等,但我希望这不会增加太多。我很高兴地报告,这一基本期望得到了经验的证实。就性能而言,它并没有完全达到Java实现的标准(我似乎记得大多数基准测试都慢了1.5倍,不过我必须重新检查一下);我希望最终能改进这一点,不过目前性能调整的优先级更高。

    您同时提出了几个问题。Re:现实生活中,总有一个权衡。可变数据结构往往需要更少的分配,因此它们减轻了GC的压力,并导致更少的内存碎片。当数据必须在线程之间共享时,不可变数据结构更安全、更方便,并且可能更高效。