在Prolog中附加反转列表时发生了什么?

时间:2014-04-21 23:08:08

标签: prolog reverse

我不太明白这段代码发生了什么:

reverse2([],[]).
reverse2([H|T],R):- reverse2(T,R2), append(R2,[H],R).

对我来说,在追加之前我们正在递归是没有意义的。

有人可以解释在达到基本案例后每个元素H是如何附加的吗?

感谢。

2 个答案:

答案 0 :(得分:2)

递归位于列表的尾部。考虑列表[1,2,3]。第一条规则不匹配。第二条规则匹配,统一H = 1T = [2,3]。然后我们打电话给reverse2([2,3], R2)。同样,第一条规则不匹配。第二条规则匹配,统一H = 2T = [3]。你可以看到来自这里的递归最终将达到第一个规则。缩放到H = 1T = [2, 3]的最外层呼叫,我们将结束R2 = [3, 2]。然后会发生追加,最后粘贴[1]

您可能会发现像这样执行示例查询很有帮助:

?- trace, reverse2([1,2,3], X).

这将向您展示查询如何展开以及每个变量的绑定。

考虑递归函数的方法是归纳的。看看基本情况。基本情况应该是真实的,它是 - 空列表的反面确实是空列表。然后看一下归纳案例。假设它适用于较小的N大小列表,它是否适用于大小为N + 1的列表? 假设它可以反转列表的尾部(size = N),并且看到如果这是真的,将头部追加到末尾将使其适用于N + 1。这就是你需要相信归纳工作的全部内容。如果你相信这两件事,你就会相信所有相信的东西,它会适用于每一个输入。因此,让自己摆脱相信任何其他步骤的需要。 :)

答案 1 :(得分:1)

为了对Daniel的描述做一点建议,你可以阅读递归条款:

reverse2([H|T], R) :- reverse2(T, R2), append(R2, [H], R).

如:

如果R[H|T]相反,则

R2与列表T相反,R是列表[H] }附加到列表R2的末尾。

或者,有点必要,如:

要反转列表,请首先反转其尾部,然后将原始列表的头部附加到该结果

因此,在该描述中,reverse2(T, R2)的递归目标首先出现。

本案例中的定义是深度优先。它将继续递归,直到它达到琐碎的基础案例,然后从每个目标返回执行追加:

(1) reverse2([1,2,3], R) :- reverse2([2,3], R2), append(R2, [1], R).
(2) reverse2([2,3], R) :- reverse2([3], R2), append(R2, [2], R).
(3) reverse2([3], R) :- reverse2([], R2), append(R2, [3], R).
(4) reverse2([], []).

然后返回:

(3) reverse2([3], R) :- reverse2([], []), append([], [3], R). % R = [3]
(2) reverse2([2,3], R) :- reverse2([3], [3]), append([3], [2], R). % R = [3,2]
(1) reverse2([1,2,3], R) :- reverse2([2,3], [3,2]), append([3,2], [1], R).

结果:

reverse2([1,2,3], [3,2,1]).

顺便说一句,这实际上是一种执行reverse/2谓词的低效方法。如果你在reverse(L, [1,2,3])找到第一个解决方案之后会遇到一些问题。有趣的是,如果你交换递归和append/3查询:

reverse2([H|T], R) :- append(R2, [H], R), reverse2(T, R2).

这实际上在reverse2(L, [1,2,3])上表现得更好,但在找到reverse2([1,2,3], L)的第一个解决方案后出现问题。与递归查询首先出现的情况相反。有了这个定义,如果第一个参数是绑定但不是第二个,那么当append(R2, [H], R)R2最初绑定在R之前,它首先找不到append/3的无关可能解决方案的效率很低。通过后续递归找到正确的解决方案。然后在找到该解决方案之后,它无限地尝试从回溯到reverse/2的更多不相关的潜在解决方案。

如果在SWI Prolog实现中查找{{1}}的源代码,它会使用纯递归和差异列表来克服这些缺点。