Prolog从尾部查找列表中的最大整数

时间:2013-11-12 22:02:27

标签: list prolog tail-recursion failure-slice

我需要从列表的头部和尾部找到列表中的最大整数。我已经写了一个程序,可以找到最大的头部现在我需要一些帮助从尾部做到这一点。

这是我到目前为止所做的:

largest([X],X).
largest([X|Xs],X) :- largest(Xs,Y), X>=Y.
largest([X|Xs],N) :- largest(Xs,N), N>X.

请记住,这会从头部找到最大的整数,我需要它从尾部开始工作。谢谢你的帮助。

3 个答案:

答案 0 :(得分:6)

等一下!在继续之前,首先测量谓词所需的时间!

?- length(J,I), I>10, append(J,[2],L),maplist(=(1),J),  time(largest(L,N)).
% 12,282 inferences, 0.006 CPU in 0.006 seconds (99% CPU, 1977389 Lips)
J = [1, 1, 1, 1, 1, 1, 1, 1, 1|...],
I = 11,
L = [1, 1, 1, 1, 1, 1, 1, 1, 1|...],
N = 2 ;
% 4 inferences, 0.000 CPU in 0.000 seconds (84% CPU, 98697 Lips)
% 24,570 inferences, 0.011 CPU in 0.011 seconds (99% CPU, 2191568 Lips)
J = [1, 1, 1, 1, 1, 1, 1, 1, 1|...],
I = 12,
L = [1, 1, 1, 1, 1, 1, 1, 1, 1|...],
N = 2 ;
% 4 inferences, 0.000 CPU in 0.000 seconds (84% CPU, 98556 Lips)
% 49,146 inferences, 0.021 CPU in 0.021 seconds (100% CPU, 2365986 Lips)
J = [1, 1, 1, 1, 1, 1, 1, 1, 1|...],
I = 13,
L = [1, 1, 1, 1, 1, 1, 1, 1, 1|...],
N = 2 ...

每次长度增加1时,推论的数量明显增加一倍!这就是Prolog如何通过极低效率获得其糟糕声誉的方式,并且在处理器速度方面取得了所有进展。

那么您的计划中发生了什么?没有必要详细介绍,但我们可以考虑一个程序的小片段()。虽然这个结果程序完全不能满足您的目的,但它为我们提供了程序中推理数量的下限

largest([X],X) :- false.
largest([X|Xs],X) :- largest(Xs,Y), false, X>=Y.
largest([X|Xs],N) :- largest(Xs,N), false, N>X.

对于列表中的每个元素,我们有两个同样适用的选项。因此,对于N元素列表,我们有2^N个选项!

这是一个可能的重写:

largest([X],X).
largest([X|Xs],R) :-
   largest(Xs,Y),
   (  X>=Y, R = X
   ;  Y > X, R = N
   ).

使用if-then-else ...

可以做得更好
largest([X],X).
largest([X|Xs],R) :-
   largest(Xs,Y),
   (  X>=Y -> R = X
   ;  Y > X, R = N
   ).

max/2

largest([X],X).
largest([X|Xs],R) :-
   largest(Xs,Y),
   R is max(X,Y).

此程序仍需要与列表长度成比例的空间。通过使用尾递归版本,您可以将其减少为常量。但至少这个版本现在以线性时间运行。

对于您想要执行的实际优化,请阅读

SWI-Prolog: Sum-List

答案 1 :(得分:1)

尾部递归头部优先解决方案如下所示:

largest( [X|Xs] , Max ) :- largest( Xs , X , Max ) .

largest( []     , R , R ) .
largest( [X|Xs] , T , R ) :- X >  T , largest( Xs , X , R ) .
largest( [X|Xs] , T , R ) :- X =< T , largest( Xs , T , R ) .

largest/2只需调用largest/3,将其累加器用列表的头部(最初的&#39; max&#39;值)播种。当largest/3向下递归列表时,它会用新的&#34;当前&#34;替换该累加器。遇到它们时的最大值。当列表用尽时,累加器具有整个列表的最大值。

您的初步解决方案:

largest([X],X).
largest([X|Xs],X) :- largest(Xs,Y), X>=Y.
largest([X|Xs],N) :- largest(Xs,N), N>X.

先尾跑。它会递归到列表的末尾,此时它会确定列表中的 last 项是最初的&#34; max&#34;值。当它在向上弹出堆栈时,它会将其与前一个值进行比较并完成所需的操作。

您的方法存在问题有两方面:

  • 它以类似于O(n 2 )的时间运行,因为它必须在每次失败时重复遍历列表。
  • 它消耗了堆栈空间,你给出了一个足够长度的列表,由于你遇到的堆栈溢出,你无法计算解决方案。

另一方面,尾部递归&#34;头部优先&#34;方法在O(n)时间运行:列表只迭代一次,最后你有解决方案。此外,由于尾递归优化,递归调用实质上转换为迭代,这意味着在初始堆栈帧之外不会消耗堆栈空间。这意味着可以为任何长度的列表计算解决方案(假设您愿意等待答案)。

答案 2 :(得分:1)

惯用的,尾递归的,头优先版本:

largest([X|Xs], O) :- largest(Xs, X, O).

largest([], O, O).
largest([X|Xs], M, O) :-
    M1 is max(X, M),
    largest(Xs, M1, O).