我目前正在尝试在Bron-Kerbosch算法的Clojure实现中正确有效地使用集合和clojure.set
命名空间并遇到困难。
我正在尝试重构我当前的实现
(defn BK [r p x graph]
(if (and (empty? p) (empty? x))
[(set r)]
(loop [p p, x x, cliques []]
(if (empty? p)
cliques
(let [v (first p)
nv (graph v)
cliques (into cliques
(BK (into r (list v))
(filter nv p)
(filter nv x)
graph))
p (rest p)
x (into x (list v))]
(recur p x cliques))))))
进入使用clojure.set
命名空间的东西
(defn BK3 [r p x graph]
(if (and (empty? p) (empty? x))
[(set r)]
(loop [p p, x x, cliques []]
(if (empty? p)
cliques
(let [v (first p)
nv (graph v)
cliques (into cliques
(BK3 (clojure.set/union (set (list v)) r)
(clojure.set/difference p nv)
(clojure.set/difference x nv)
graph))
p (rest p)
x (clojure.set/union (set (list v)) x)]
(recur p x cliques))))))
(defn get-BK3 [graph]
(BK3 (set '()) (set (doall (range (count graph)))) (set '()) graph))
虽然这个当前实现会导致StackOverflow。这是一个简短的SSCCE,其中包含运行评估函数http://pastebin.com/PVxJidGB的所有必要代码。
如果我在函数prn
之前放置一个(if (empty? p))
表单,我可以看到集合p
已更改一次而且从未再次更改,也就是集x
永远不会被添加到。打印以下内容并重复执行,直到发生堆栈溢出。
finalproject.core> (get-BK3 (random-graph 10 20))
"P: " #{0 7 1 4 6 3 2 9 5 8} " X: " #{}
-----------------
"P: " #{0 7 4 6 3 2 9 8} " X: " #{}
-----------------
"P: " #{0 7 4 6 3 2 9 8} " X: " #{}
-----------------
"P: " #{0 7 4 6 3 2 9 8} " X: " #{}
-----------------
....
这必须意味着在每次递归调用BK3
时,集p
都没有从中删除集nv
?虽然正在审核clojure.set/difference
help page,但它应该这样做吗?我是否读错页面或者有一些错误导致堆栈溢出?
这是我不明白的第一个问题。我的下一个问题是first
和rest
不返回集合(#{0 1 2}
)而是列表((0 1 2)
)。如果将列表传递给任何clojure.set
函数,则会抛出错误。是否有first
和rest
可以选择返回集合?
编辑:这是来自维基百科的伪代码实现,具有正确的设置概念符号。我猜我对符号的解释可能不正确?
答案 0 :(得分:2)
正如@michat回答的那样,在维基百科公式中,递归调用是使用set intersection 而不是设置 difference ,它们是不一样的。在数学中,clojure.set/difference
的匹配函数是set complement。
对于first
和rest
与set
的问题,您可以使用first
,这将产生下一个不需要的元素(但它在算法中并不重要)和disj
从集合中删除所选元素。
注意,您可以按(set '())
简化#{}
。
以下是clojure.set
的工作版本,其中包含一个非常快速的测试/工作台,可显示set
版本的性能改进:
(require '[clojure.set :as s])
(defn BK4 [r p x graph]
(if (and (empty? p) (empty? x))
[r] ;; r is already a set
(loop [p p, x x, cliques []]
(if (empty? p)
cliques
(let [v (first p) ;; p is a set, first is not necessary the next in sequence
nv (graph v) ;; take v-th set from graph
cliques (concat cliques
(BK4 (conj r v) ;; add v to the set r
(s/intersection p nv)
(s/intersection x nv)
graph))]
(recur (disj p v) (conj x v) cliques))))))
(defn get-BK4 [graph]
(BK4 #{} (set (range (count graph))) #{} graph))
测试:
(let [graph (doall (random-graph 1000 1000))
bk (time (get-BK graph))
bk4 (time (get-BK4 graph))]
(if (= bk bk4)
(println "Seems ok")
(println "ko")))
打印(在MBP 2,5 GHz Intel Core i7上)
"经过时间:243.533768 msecs"
"经历时间:19.228952 msecs"
好像
答案 1 :(得分:1)
来自维基百科的此算法的伪代码版本中的递归调用使用集合交集,而不是设置差异。您可以使用clojure.set/intersection
来计算两个持久集的交集。
对于每个循环,正文的第二行使用设置差异。在您的代码中,相应的表达式为(rest p)
,但这会生成p
除(first p)
之外的所有元素的序列,而不是集合。您需要使用(disj p (first p))
代替(disj p v)
,或者使用之前介绍的本地set/union
。
into
和conj
对于向集合中添加单个元素都是过度的 - 改为使用{{1}}。
答案 2 :(得分:0)
由于您正在使用StackOverflow,因此从概念上讲,这可能发生在您的代码中:
(clojure.set/difference #{1,2,3,4,5} #{4})
#{1 3 2 5}
user> (clojure.set/difference #{1,2,3,5} #{4})
#{1 3 2 5}
其中p
为#{1 2 3 4 5}
而nv
为#{4}
。
在递归中,p变为#{1 3 2 5}
然后再次得到v
(因为你没有自循环,因此4之前不是v
...)因此相同{ {1}} nv
即。两组中的第一组是相同的..
#{4}
再次相同的递归..和stackoverflow ..