我有一个节点列表,每个节点都有一个父节点,我想用这些节点构建一个树。
(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 在树的情况下,我对节点的每个子进行递归。
我正在寻找一个不会遇到这个问题的调整后的解决方案 如果您对此问题有完全不同的解决方案,我很乐意看到它 我读了一下有关拉链的内容,也许这是建造树木的更好方法。
答案 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/prewalk
和postwalk
将在足够高的树上溢出堆栈。)