我代表一棵树
A
/ \
B C
/\
D E
类似于clojure向量中的[A [B [D] [E]] [C]]
。我需要使用广度优先搜索来找到目标的最短路径。我的实际矢量看起来像这样 -
["33L" ["32R" ["31L" ["30R" [false]] ["11R" ["01L" ["00R" [true]]] ["00L" [false]]]] ["30L" [false]] ["22L" ["11R" ["01L" ["00R" [true]]] ["00L" [false]]] ["02R" ["01L" ["00R" [true]]] ["00L" [false]]]]] ["31R" ["30L" [false]] ["11L" ["01R" ["00L" [false]]] ["00R" [true]]]] ["22R" ["11L" ["01R" ["00L" [false]]] ["00R" [true]]] ["02L" ["01R" ["00L" [false]]] ["00R" [true]]]]]
所有以true结尾的节点都是我感兴趣的节点。有多个节点以true结尾。我需要找到从“33L”的最短路径,这是第一个节点到真节点。例如,如果您查看顶部的树并假设D为真且C为真。从A到A真正节点的最短路径是A - > ç
我想出了如何打印所有节点,就像使用广度优先算法搜索它们一样。
(defn bfs [& nodes]
(if (seq nodes)
(concat (map first nodes)
(apply bfs (apply concat (map rest nodes))))))
在树上运行它给了我 -
("33L" "32R" "31R" "22R" "31L" "30L" "22L" "30L" "11L" "11L" "02L" "30R" "11R" false "11R" "02R" false "01R" "00R" "01R" "00R" "01R" "00R" false "01L" "00L" "01L" "00L" "01L" "00L" "00L" true "00L" true "00L" true "00R" false "00R" false "00R" false false false false true true true)
这正是广度优先搜索将如何浏览整个数据集。但是,我无法弄清楚我如何找到并打印到真节点的最短路径。在你问我之前,我已经看过其他广泛的堆栈溢出算法。
更新
我看过clojure拉链,它解决了我的问题。然而,它首先是深度,我需要先宽广的东西。有没有办法可以使用z / down,z / left,z / right函数创建一个广度的第一个包装器?
答案 0 :(得分:1)
广度优先搜索是搜索。在这里您返回步行顺序,如果您需要最短路径,则必须修改搜索算法实现以某种方式跟踪路径。这意味着您展开的每个节点都应该包含有关它的最短路径的信息。您可以通过传递此信息手动完成。
Clojure几乎内置zippers
,允许走树。您可能想要使用它们。
(require '[clojure.zip :as z])
> (def a ["0" ["1L" [false] [false]] ["1R" [true]]])
> ;create a zipper where vector? is a branch and (vec (rest %)) are
> ;childrens
> (def ztree (z/zipper vector? (comp vec rest) nil a))
> ztree
[["0" ["1L" [false] [false]] ["1R" [true]]] nil]
> (vector? ztree) ;zipper is vector of your original vector and some aaditional info
true
> (meta ztree) ;here are the functions
{:zip/make-node nil,
:zip/children #<core$comp$fn__4192 clojure.core$comp$fn__4192@59bdcbda>,
:zip/branch? #<core$vector_QMARK_ clojure.core$vector_QMARK_@319449e4>}
> (-> ztree z/down z/down z/up z/right z/down) ;walk the tree
[[true]
{:l [],
:pnodes [["0" ["1L" [false] [false]] ["1R" [true]]] ["1R" [true]]],
:ppath {:l [["1L" [false] [false]]],
:pnodes [["0" ["1L" [false] [false]] ["1R" [true]]]],
:ppath nil,
:r nil},
:r nil}]
所以你可以看到,我如何手动走树和拉链结构是一个带有first
位置的当前分支或节点的向量和一个带有
:l
和:r
中的左右兄弟,:pnodes
中此节点的路径(不是步行顺序),:ppath
中此节点的父拉链结构。
因此,您使用拉链,然后在:pnodes
跟踪您的路径。
P.S。发表评论后
如果您需要实施,那么就像教科书一样:
(defn queue [& vals]
(apply merge (clojure.lang.PersistentQueue/EMPTY) vals))
(defn bfs [tree]
(loop [expanding (queue {:node tree
:path []})]
(if (empty? expanding)
nil
(let [{[val & childs] :node
p :path}
(peek expanding)
curr-path (conj p val)]
(if (true? val)
p
(recur
(apply merge (pop expanding)
(map #(hash-map :node %
:path curr-path)
childs))))))))
> (bfs your-vector)
["33L" "31R" "11L" "00R"]
但看起来最好自己尝试解决这个问题。
P.P.S。 您可以根据需要使用拉链走路。只需修改您的代码即可使用树上方的拉链,您将获得两者,步行顺序和最短路径。
答案 1 :(得分:0)
@JustAnotherCurious有正确的答案,但我会用稍微不同的方式表达BFS的实现,以获得更加惯用的风格(使用when-let
,而不是if
,nil
和let
,并使用nil-punning代替empty?
。我还隐藏了接口后面的树的数据表示,以便在必要时更容易更改:
(defn goal? [[val]]
(true? val))
(defn leaf? [tree]
(= 1 (count tree)))
(defn tree-val [tree]
(first tree))
(defn children [tree]
(rest tree))
(defn queue [& vals]
(apply conj clojure.lang.PersistentQueue/EMPTY vals))
(defn bfs [root]
(loop [q (queue {:tree root :path []})]
(when-let [{:keys [tree path]} (peek q)]
(cond
(goal? tree) path
(leaf? tree) (recur (pop q))
:else
(let [new-path (conj path (tree-val tree))
wrap (fn [t] {:tree t :path new-path})]
(recur (->> (children tree)
(map wrap)
(apply conj (pop q)))))))))