我不太明白这段代码发生了什么:
reverse2([],[]).
reverse2([H|T],R):- reverse2(T,R2), append(R2,[H],R).
对我来说,在追加之前我们正在递归是没有意义的。
有人可以解释在达到基本案例后每个元素H是如何附加的吗?
感谢。
答案 0 :(得分:2)
递归位于列表的尾部。考虑列表[1,2,3]
。第一条规则不匹配。第二条规则匹配,统一H = 1
和T = [2,3]
。然后我们打电话给reverse2([2,3], R2)
。同样,第一条规则不匹配。第二条规则匹配,统一H = 2
和T = [3]
。你可以看到来自这里的递归最终将达到第一个规则。缩放到H = 1
和T = [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}}的源代码,它会使用纯递归和差异列表来克服这些缺点。