避免无限递归,但仍然只使用未绑定的参数传递

时间:2016-10-11 16:35:08

标签: recursion prolog graph-algorithm undirected-graph bound-variable

我有以下工作程序:(可在此网站上测试:http://swish.swi-prolog.org,我已删除指向已保存程序的直接链接,因为我注意到任何人都可以编辑它。)

它在无向图中搜索两点之间的路径。重要的是,结果在“主”谓词的范围内返回。 (在Track变量中)

edge(a, b).
edge(b, c).
edge(d, b).
edge(d, e).
edge(v, w).

connected(Y, X) :-
    (
        edge(X, Y);
        edge(Y, X)
    ).

path(X, X, _, []) :-
    connected(X, _).

path(X, Y, _, [X, Y]) :-
    connected(Y, X).

path(X, Z, Visited, [X|Track]) :-
    connected(X, Y),
    not(member(X, Visited)),
    path(Y, Z, [X|Visited], Track).

main(X, Y) :-
    path(X, Y, [], Track),
    print(Track),
    !.

结果:

?- main(a, e).
[a, b, d, e]
true

?- main(c, c).
[]
true

?- main(b, w).
false

我的问题:

  1. 访问节点列表以两种不同的方式传递给谓词。在绑定的Visited变量和未绑定的Track变量中。这两种不同形式的参数传递有哪些名称?

  2. 通常我只想使用未绑定的参数传递(Track变量),以使结果在主谓词的范围内。但是我也必须添加Visited变量,因为成员检查在Track变量上不起作用(我不知道为什么)。是否有可能只使用未绑定的方式传递Track? (没有Visited变量)

  3. 非常感谢!

1 个答案:

答案 0 :(得分:1)

简短的回答:不,你不能避免额外的争论而不会使一切变得更加混乱。这是因为这种寻找路径的特定算法需要保持状态;基本上,你的额外论点是你的状态。

可能还有其他方法来保持状态,例如使用全局变量变量或动态更改Prolog数据库,但两者都更难以正确处理,并且涉及更多代码。

这个额外的参数通常被称为累加器,因为它会随着proof tree向下累积。最简单的例子是遍历一个列表:

foo([]).
foo([X|Xs]) :-
    foo(Xs).

这很好,除非你在到达之前需要知道你已经看过的元素:

bar(List) :-
    bar_(List, []).

bar_([], _).
bar_([X|Xs], Acc) :-
    /* Acc is a list of all elements so far */
    bar_(Xs, [X|Acc]).

这与您在代码中执行的操作大致相同。如果你特别注意这一点:

path(X, Z, Visited, /* here */[X|Track]) :-
    connected(X, Y),
    not(member(X, Visited)),
    path(Y, Z, [X|Visited], /* and here */Track).

path/4的最后一个参数在证明树中深度少一个一个元素!当然,第三个参数是一个更长的时间(随着你走向证明树,它会增长)。

例如,您可以通过向上面的愚蠢bar谓词添加另一个参数来反转列表:

list_reverse(L, R) :-
    list_reverse_(L, [], R).

list_reverse_([], R, R).
list_reverse_([X|Xs], R0, R) :-
    list_reverse_(Xs, [X|R0], R).

我不知道最后一个参数的任何特殊名称,一个在开头是免费的并且在最后保存解决方案。在某些情况下,它可能是输出参数,因为它意味着在以某种方式转换输入后捕获输出。在许多情况下,最好避免将参数视为严格的输入或输出参数。例如,length/2

?- length([a,b], N).
N = 2.

?- length(L, 3).
L = [_2092, _2098, _2104].

?- length(L, N).
L = [],
N = 0 ;
L = [_2122],
N = 1 ;
L = [_2122, _2128],
N = 2 . % and so on

注意:您的代码存在一些不重要的小问题,并且在Stackoverflow上提供这么多建议并不是一个好主意。如果您愿意,可以在Code Review上将其作为问题提交。

修改:您一定要学习this question

我还提供了一个更简单的解决方案here。请注意使用term_expansion/2在编译时从无向边创建有向边。更重要的是:你不需要main,只需从顶层调用你想要的谓词即可。当您删除剪切时,当From和To参数中的一个或两个都是自由变量时,您将获得所有可能的解决方案。