在树中,如何找到有子叶的树节点的路径?

时间:2019-05-13 18:23:29

标签: clojure

基本上,我正在尝试实现此算法,尽管也许有更好的方法可以解决它。

  • 从根开始
  • 检查当前节点的每个子节点是否有叶子子节点(子节点)
  • 如果当前节点的任何子节点都有叶子,请记录到当前节点(而不是子节点)的路径,并且不要再沿着该路径继续走下去。
  • 否则继续DFS

非功能性伪代码:

def find_paths(node):
    for child in node.children:
       if child.children.len() == 0
          child_with_leaf = true
    if child_with_leaf
       record path to node
    else
       for child in node.children
           find_paths(child)

例如:

:root
  |- :a
  |   +- :x
  |       |- :y
  |       |   +- :t
  |       |       +- :l2
  |       +- :z
  |          +- :l3
  +- :b
      +- :c
          |- :d
          |   +- :l4
          +- :e
              +- :l5

结果将是:

[[:root :a]
 [:root :b :c]]

这是我在Clojure中的表现:

(defn atleast-one?
  [pred coll]
  (not (nil? (some pred coll))))

; updated with erdos's answer
(defn children-have-leaves?
  [loc]
  (some->> loc
           (iterate z/children)
           (take-while z/branch?)
           (atleast-one? (comp not empty? z/children))))

(defn find-paths
  [tree]
  (loop [loc (z/vector-zip tree)
         ans nil]
    (if (z/end? loc)
      ans
      (recur (z/next loc)
             (cond->> ans
                      (children-have-leaves? loc)
                      (cons (->> loc z/down z/path (map z/node)))))))
  )

(def test-data2
  [:root [:a [:x [:y [:t [:l2]]] [:z [:l3]]]] [:b [:c [:d [:l4]] [:e [:l5]]]]]
  )

更新:使用下面的erdos回答解决了崩溃问题,但是我认为我的代码仍然存在问题,因为这会打印出所有路径,而不是所需的路径。

3 个答案:

答案 0 :(得分:2)

该异常来自您的children-have-leaves?函数。

(not (empty? z/children))表达式失败,因为z/children是一个函数,但是empty?必须在集合上调用。

您需要的是一个谓词,如果节点有子节点,则返回true,例如:(fn [x] (not (empty? (z/children x))))或更短的节点:(comp not empty? z/children)

正确的实现方式:

(defn children-have-leaves?
  [loc]
  (some->> loc
           (iterate z/children)
           (take-while z/branch?)
           (atleast-one? (comp not empty? z/children))))

答案 1 :(得分:2)

我认为您已经参考了我以前与拉链有关的answer。但是请注意,我先前的答案按原样使用vector-zip,因此您必须像vector-zip一样浏览它-您可能必须将头放在两个光标的工作方式上。为了简化导航,建议您为树形结构创建自己的拉链。即

(defn my-zipper [root]
  (z/zipper ;; branch?
            (fn [x]
              (when (vector? x)
                (let [[n & xs] x] (and n (-> xs count zero? not)))))
            ;; children
            (fn [[n & xs]] xs)
            ;; make-node
            (fn [[n & _] xs] [n xs])
            root))

然后解决方案将类似于我的其他答案:

(def test-data2
  [:root 
   [:a 
    [:x 
     [:y 
      [:t [:l2]]] 
     [:z [:l3]]]] 
   [:b 
    [:c 
     [:d [:l4]] 
     [:e [:l5]]]]])

(->> test-data2
     my-zipper
     (iterate z/next)
     (take-while (complement z/end?))
     (filter (comp children-with-leaves? z/node))
     (map #(->> % z/path (map z/node)))
     set)
;; => #{(:root :a :x) (:root :a :x :y) (:root :b :c)}

主要逻辑简化为:

(defn children-with-leaves? [[_ & children]]
  (some (fn [[c & xs]] (nil? xs)) children))

答案 2 :(得分:0)

如果您要处理树状数据结构,强烈建议使用the tupelo.forest library

不过,我不明白您的目标。您的示例中的节点:a:c与最近的叶子没有相等的距离。

实际上,我只是注意到,您示例中的树 与代码尝试中的树不同。您能否更新问题以使其一致?


以下是您如何执行此操作的示例:

(dotest ; find the grandparent of each leaf
  (hid-count-reset)
  (with-forest (new-forest)
    (let [data              [:root
                             [:a
                              [:x
                               [:y [:t [:l2]]]
                               [:z [:l3]]]]
                             [:b [:c
                                  [:d [:l4]]
                                  [:e [:l5]]]]]
          root-hid          (add-tree-hiccup data)
          leaf-paths        (find-paths-with root-hid [:** :*] leaf-path?)
          grandparent-paths (mapv #(drop-last 2 %) leaf-paths)
          grandparent-tags  (set
                              (forv [path grandparent-paths]
                                (let [path-tags (it-> path
                                                  (mapv #(hid->node %) it)
                                                  (mapv #(grab :tag %) it))]
                                  path-tags)))]
      (is= (format-paths leaf-paths)
        [[{:tag :root} [{:tag :a} [{:tag :x} [{:tag :y} [{:tag :t} [{:tag :l2}]]]]]]
         [{:tag :root} [{:tag :a} [{:tag :x} [{:tag :z} [{:tag :l3}]]]]]
         [{:tag :root} [{:tag :b} [{:tag :c} [{:tag :d} [{:tag :l4}]]]]]
         [{:tag :root} [{:tag :b} [{:tag :c} [{:tag :e} [{:tag :l5}]]]]]])
      (is= grandparent-tags
          #{[:root :a :x] 
            [:root :a :x :y] 
            [:root :b :c]} ))))