let rec f n =
match n with
| 0 | 1 | 2 -> 1
| _ -> f (n - 2) + f (n - 3)
如果没有CPS或Memoization,它怎么能被尾递归?
答案 0 :(得分:4)
let f n = Seq.unfold (fun (x, y, z) -> Some(x, (y, z, x + y))) (1I, 1I, 1I)
|> Seq.nth n
甚至更好:
let lambda (x, y, z) = x, (y, z, x + y)
let combinator = Seq.unfold (lambda >> Some) (1I, 1I, 1I)
let f n = combinator |> Seq.nth n
要了解此处发生的情况,请参阅this代码段。它定义了Fibonacci算法,你的非常相似。
UPD 这里有三个组成部分:
i
- 元素; i
上运行递归;和你已经要求一个尾递归代码,实际上有两种方法:像@Tomas一样制作你自己的组合子,或者利用现有的组合Seq.unfold
,这当然是尾递归的。我更喜欢第二种方法,因为我可以将整个代码分成一组let
语句。
答案 1 :(得分:4)
@bytebuster的解决方案很好,但他没有解释他是如何创建它的,所以它只会在你解决这个特定问题时有所帮助。顺便说一下,你的公式看起来有点像Fibonacci(但不完全),可以是calculated analytically without any looping(即使没有隐藏在Seq.unfold
中的循环)。
您开始使用以下功能:
let rec f0 n =
match n with
| 0 | 1 | 2 -> 1
| _ -> f0 (n - 2) + f0 (n - 3)
该函数为参数f0
和n - 2
调用n - 3
,因此我们需要知道这些值。诀窍是使用dynamic programming(可以使用memoization完成),但由于你不想使用memoization,我们可以手工编写。
我们可以编写f1 n
,它返回一个三元素元组,其当前值和两个过去值为f0
。这意味着f1 n = (f0 (n - 2), f0 (n - 1), f0 n)
:
let rec f1 n =
match n with
| 0 -> (0, 0, 1)
| 1 -> (0, 1, 1)
| 2 -> (1, 1, 1)
| _ ->
// Here we call `f1 (n - 1)` so we get values
// f0 (n - 3), f0 (n - 2), f0 (n - 1)
let fm3, fm2, fm1 = (f1 (n - 1))
(fm2, fm1, fm2 + fm3)
这个函数不是尾部重新定义的,但它只能递归地调用一次,这意味着我们可以使用累加器参数模式:
let f2 n =
let rec loop (fm3, fm2, fm1) n =
match n with
| 2 -> (fm3, fm2, fm1)
| _ -> loop (fm2, fm1, fm2 + fm3) (n - 1)
match n with
| 0 -> (0, 0, 1)
| 1 -> (0, 1, 1)
| n -> loop (1, 1, 1) n
我们需要特别在0
的主体中处理参数1
和fc
。对于任何其他输入,我们从最初的三个值(即(f0 0, f0 1, f0 2) = (1, 1, 1)
)开始,然后循环n次执行给定的递归步骤,直到达到2.递归loop
函数是@bybbuster的解决方案实现的使用Seq.unfold
。
所以,你的函数有一个尾递归版本,但这只是因为我们可以简单地将过去的三个值保存在一个元组中。通常,如果计算所需的先前值的代码更复杂,则可能无法实现这一点。
答案 2 :(得分:3)
甚至比尾递归方法更好,您可以利用矩阵乘法来减少任何像使用O(log n )运算的解决方案那样的重现。我把正确的证据作为读者的练习。
module NumericLiteralG =
let inline FromZero() = LanguagePrimitives.GenericZero
let inline FromOne() = LanguagePrimitives.GenericOne
// these operators keep the inferred types from getting out of hand
let inline ( + ) (x:^a) (y:^a) : ^a = x + y
let inline ( * ) (x:^a) (y:^a) : ^a = x * y
let inline dot (a,b,c) (d,e,f) = a*d+b*e+c*f
let trans ((a,b,c),(d,e,f),(g,h,i)) = (a,d,g),(b,e,h),(c,f,i)
let map f (x,y,z) = f x, f y, f z
type 'a triple = 'a * 'a * 'a
// 3x3 matrix type
type 'a Mat3 = Mat3 of 'a triple triple with
static member inline ( * )(Mat3 M, Mat3 N) =
let N' = trans N
map (fun x -> map (dot x) N') M
|> Mat3
static member inline get_One() = Mat3((1G,0G,0G),(0G,1G,0G),(0G,0G,1G))
static member (/)(Mat3 M, Mat3 N) = failwith "Needed for pown, but not supported"
let inline f n =
// use pown to get O(log n) time
let (Mat3((a,b,c),(_,_,_),(_,_,_))) = pown (Mat3 ((0G,1G,0G),(0G,0G,1G),(1G,1G,0G))) n
a + b + c
// this will take a while...
let bigResult : bigint = f 1000000