将递归转换为尾递归?

时间:2013-03-07 22:59:25

标签: prolog exponentiation logic-programming

我正在尝试编写一个谓词,递归地找到某个数字的n次幂 [A ^ n = A * A ^(n-1)]并使用快捷键A ^(2n)= A ^ n * A ^ n。

到目前为止,这是解决方案。

p(_,0,1):-!.
p(A,N,R):-N mod 2=0,!,N1=N/2,p(A,N1,R1),R=R1*R1.
p(A,N,R):-N1=N-1,p(A,N1,R1),R=R1*A.

现在我想让这个尾递归。我可以为简单的情况做尾部,例如没有快捷方式的阶乘和幂(通过添加累加器),但这个很难。

非常感谢任何帮助!

2 个答案:

答案 0 :(得分:3)

看起来它毕竟是可能的,只是从另一端开始:

pow(A,N,R) :-
    pow(A,N,A,1,R).

pow(_,N,R,N,R) :- !.
pow(A,N,Acc,M,R) :-
    M =< N div 2, !,
    M1 is M*2,
    NewAcc is Acc * Acc,
    pow(A,N,NewAcc,M1,R).
pow(A,N,Acc,M,R) :-
    M < N,
    M1 is M+1,
    NewAcc is A * Acc,
    pow(A,N,NewAcc,M1,R).

它将快捷方式应用于小于N的2的最高功率,这无疑与您的算法正在做的不同。

答案 1 :(得分:2)

鲍里斯是正确的,他的算法所做的与原始算法不同。但是如果你真的想要,你可以在这里重现它:

注意您可以从数字的二进制表示中确定操作的顺序。设N=7,然后二进制N=111,表示为N=7~111

现在您可以在原始算法中看到该方案:

N      Op     N'
7~111  Mul    6~110 (= zero last bit) 
6~110  Squ    3~011 (= shift right)
3~011  Mul    2~010 
2~010  Squ    1~001
1~001  Base

考虑到由于算法的递归性质,这些步骤是从上到下执行的,您得到Base - Squ - Mul - Squ - Mul = ((A*A)*A)*((A*A)*A))*A = A**7

将此与鲍里斯算法对比:

N      Op     N'
1~001  Squ    2~010 (=shift left)
2~010  Squ    4~100 (=shift left)
4~100  Mul    5~101 (=add one)
5~101  Mul    6~110 (=add one)
6~110  Mul    7~111 (=add one)

所以这个首先完成所有的移位,而原来考虑每个位除了N的第一个,从右到左依次“排队”(因为自下而上)Mul, Squ如果位如果未设置,则设置为Squ

要重现此行为(效率更高,因为您永远不会进行比正方形更简单的乘法),您可以从二进制的N开始并执行以下操作(此处通常为伪代码,对您来说很容易翻译成prolog):

Acc=A
for i in (reverse(tail(bits(N)))):
    Acc*=Acc
    if i==1:
       Acc*=A

这是N>=1N=0是一个特例,必须单独处理。

我很确定这是正确的。如果您有疑问,那么只需考虑您的原始算法:测试mod 2 == 0与测试最后一位是否为零相同。如果不是,那么减去一个与将最后一位归零相同,而加倍和减半只是在二进制中向左或向右移动。