从节点,父元素列表构建树

时间:2013-06-21 21:19:15

标签: recursion clojure tree

我有一个节点列表,每个节点都有一个父节点,我想用这些节点构建一个树。

(def elems '[{:node A :parent nil} {:node B :parent A} {:node C :parent A} {:node D :parent C}])
(build-tree elems)
=> (A (B) (C (D)))

目前我有这段代码:

(defn root-node [elems]
  (:node (first (remove :parent elems))))

(defn children [elems root]
  (map :node (filter #(= root (:parent %)) elems)))

(defn create-sub-tree [elems root-node]
  (conj (map #(create-sub-tree elems %) (children elems root-node)) root-node))

(defn build-tree [elems]
  (create-sub-tree elems (root-node elems)))

在此解决方案中使用递归,但不使用循环重复语法。 这很糟糕,因为代码无法优化并且StackOverflowError is possible 如果我在每一步中都有一次递归,我似乎只能使用recur 在树的情况下,我对节点的每个子进行递归。

我正在寻找一个不会遇到这个问题的调整后的解决方案 如果您对此问题有完全不同的解决方案,我很乐意看到它 我读了一下有关拉链的内容,也许这是建造树木的更好方法。

1 个答案:

答案 0 :(得分:2)

这是我要采用的解决方案。它仍然容易受到StackOverflowError的影响,但仅适用于非常“高”的树。

(defn build-tree [elems]
  (let [vec-conj (fnil conj [])
        adj-map (reduce (fn [acc {:keys [node parent]}]
                          (update-in acc [parent] vec-conj node))
                        {} elems)
        construct-tree (fn construct-tree [node]
                         (cons node
                               (map construct-tree
                                    (get adj-map node))))
        tree (construct-tree nil)]
    (assert (= (count tree) 2) "Must only have one root node")
    (second tree)))

我们可以删除StackOverflowError问题,但这样做有点痛苦。不是用construct-tree立即处理每个叶子,而是在那里留下一些东西来表示还有更多的工作要做(比如零arg函数),然后再做一个处理步骤来处理它们中的每一个,继续处理直到有没有工作要做。可以在常量堆栈空间中执行此操作,但除非您期望真正高大的树木,否则它可能是不必要的(甚至clojure.walk/prewalkpostwalk将在足够高的树上溢出堆栈。)