我认为问题在于您只是没有编写任何逻辑来返回循环。
源自节点的循环
这并不难,您已经使用了一个累加器,您只需要一种机制,一旦找到循环,就将您的累加器作为循环返回。您可以通过向cycle 提供一个附加参数来做到这一点:
cycle(Node,Cycle) :-
cycle(Node,[],Cycle).
cycle(Curr,Visited,Cycle) :-
member(Curr,Visited),
!,
reverse([Curr|Visited],Cycle).
cycle(Curr,Visited,Cycle) :-
edge(Curr,Next),
cycle(Next,[Curr|Visited],Cycle).
在这个实现中我使用了reverse/2,因为这会给你正确的节点顺序(边缘)。如果您对此不感兴趣(例如您只想分析循环,而您可以反过来分析,您可以简单地将cycle/3 的第一个子句中的Cycle 替换为[Curr|Visited]。
然而问题是,如果你调用这个谓词,它会返回:
?- cycle(d,Cycle).
Cycle = [d, c, b, a, e, d] ;
Cycle = [d, c, b, a, e, c] ;
Cycle = [d, a, e, d] ;
Cycle = [d, a, e, c, b, a].
因此它不会搜索 d 本身是循环的一部分的循环,它会搜索所有可以源自 d 的循环。这可能不是你想要的。
具有给定节点的循环
但是,您可以重写谓词:您需要存储您起源的节点:
cycle(Node,Cycle) :-
edge(Node,Next),
cycle(Node,Next,[Node],Cycle).
所以在这里我们查找每个源自Node 的edge/2 并使用初始Node 调用cycle/4(以检查我们何时找到一个循环),Next 节点,访问的列表[Node]s 和 Cycle 参数返回找到的循环。
现在cycle/4 有三个可能的选项:
-
我们到达原始节点,在这种情况下,我们找到了Node 所在的循环,我们进行类似于第一个版本的处理:
cycle(Curr,Curr,Visited,Cycle) :-
!,
reverse([Curr|Visited],Cycle).
-
我们到达一个我们已经访问过的节点:Curr 是Visited 的一个元素,在这种情况下我们需要中断搜索:否则我们将循环无限次:
cycle(_,Curr,Visited,_) :-
member(Curr,Visited),
!,
fail.
fail 是表示分支失败的谓词。这很有用,因为现在我们指定了失败的原因。
-
最后我们只是简单地访问了另一条边并尝试找到下一个节点:
cycle(Node,Curr,Visited,Cycle) :-
edge(Curr,Next),
cycle(Node,Next,[Curr|Visited],Cycle).
完整版是:
cycle(Node,Cycle) :-
edge(Node,Next),
cycle(Node,Next,[Node],Cycle).
cycle(Curr,Curr,Visited,Cycle) :-
!,
reverse([Curr|Visited],Cycle).
cycle(_,Curr,Visited,_) :-
member(Curr,Visited),
!,
fail.
cycle(Node,Curr,Visited,Cycle) :-
edge(Curr,Next),
cycle(Node,Next,[Curr|Visited],Cycle).
生成:
?- cycle(d,Cycle).
Cycle = [d, c, b, a, e, d] ;
Cycle = [d, a, e, d] ;
因此,所有都是从d 开始并再次访问d 的循环。我们可以通过在一个子句中压缩第二种和第三种场景来使代码更高效:
cycle(Node,Cycle) :-
edge(Node,Next),
cycle(Node,Next,[Node],Cycle).
cycle(Curr,Curr,Visited,Cycle) :-
!,
reverse([Curr|Visited],Cycle).
cycle(Node,Curr,Visited,Cycle) :-
\+ member(Curr,Visited),
edge(Curr,Next),
cycle(Node,Next,[Curr|Visited],Cycle).
\+ 表示不是。