F# 中更高效的递归 Tetranacci 函数
More Efficient Recursive Tetranacci function in F#
我正在尝试尽可能高效地使用 F# 编写一个 tetranacci 函数,但我想出的第一个解决方案确实效率很低。你能帮我想出一个更好的吗?我怎样才能在线性时间内实现这个?
let rec tetra n =
match n with
| 0 -> 0
| 1 -> 1
| 2 -> 1
| 3 -> 2
| _ -> tetra (n - 1) + tetra (n - 2) + tetra (n - 3) + tetra (n - 4)
您可以通过设计一个函数来计算 4 元组下一次迭代的状态,从而节省开支。然后序列生成器函数 Seq.unfold
可用于构建包含每个状态四元组的第一个元素的序列,这是一种“惰性”操作——序列的元素仅在被消耗时按需计算.
let tetranacci (a3, a2, a1, a0) = a2, a1, a0, a3 + a2 + a1 + a0
(0, 1, 1, 2)
|> Seq.unfold (fun (a3, _, _, _ as a30) -> Some(a3, tetranacci a30))
|> Seq.take 10
|> Seq.toList
// val it : int list = [0; 1; 1; 2; 4; 8; 15; 29; 56; 108]
请注意,标准 Tetranacci 序列 (OEIS A000078) 通常会以 (0, 0, 0, 1)
:
的起始状态生成
// val it : int list = [0; 0; 0; 1; 1; 2; 4; 8; 15; 29]
kaefer's回答的不错,但是为什么停在线性时间?事实证明,您实际上可以实现对数时间,注意递归可以表示为矩阵乘法:
[T_n+1] [0; 1; 0; 0][T_n]
[T_n+2] = [0; 0; 1; 0][T_n+1]
[T_n+3] [0; 0; 0; 1][T_n+2]
[T_n+4] [1; 1; 1; 1][T_n+3]
但是然后T_n
可以通过循环n次来实现,我们可以看到M^n*[T_0; T_1; T_2; T_3]
的第一个条目(就是M^n
的右上角条目) ),我们可以通过重复平方在 O(log n) 时间内执行矩阵乘法:
type Mat =
| Mat of bigint[][]
static member (*)(Mat arr1, Mat arr2) =
Array.init arr1.Length (fun i -> Array.init arr2.[0].Length (fun j -> Array.sum [| for k in 0 .. arr2.Length - 1 -> arr1.[i].[k]*arr2.[k].[j] |]))
|> Mat
static member Pow(m, n) =
match n with
| 0 ->
let (Mat arr) = m
Array.init arr.Length (fun i -> Array.init arr.Length (fun j -> if i = j then 1I else 0I))
|> Mat
| 1 -> m
| _ ->
let m2 = m ** (n/2)
if n % 2 = 0 then m2 * m2
else m2 * m2 * m
let tetr =
let m = Mat [| [|0I; 1I; 0I; 0I|]
[|0I; 0I; 1I; 0I|]
[|0I; 0I; 0I; 1I|]
[|1I; 1I; 1I; 1I|]|]
fun n ->
let (Mat m') = m ** n
m'.[0].[3]
for i in 0 .. 50 do
printfn "%A" (tetr i)
这是一个 tail recursive 版本,主要编译为循环(其复杂度应为 O(n)):
let tetr n =
let rec t acc4 acc3 acc2 acc1 = function
| n when n = 0 -> acc4
| n when n = 1 -> acc3
| n when n = 2 -> acc2
| n when n = 3 -> acc1
| n -> t acc3 acc2 acc1 (acc1 + acc2 + acc3 + acc4) (n - 1)
t 0 1 1 2 n
acc1
对应tetra (n - 1)
,
acc2
对应tetra (n - 2)
,
acc3
对应tetra (n - 3)
,
acc4
对应tetra (n - 4)
我正在尝试尽可能高效地使用 F# 编写一个 tetranacci 函数,但我想出的第一个解决方案确实效率很低。你能帮我想出一个更好的吗?我怎样才能在线性时间内实现这个?
let rec tetra n =
match n with
| 0 -> 0
| 1 -> 1
| 2 -> 1
| 3 -> 2
| _ -> tetra (n - 1) + tetra (n - 2) + tetra (n - 3) + tetra (n - 4)
您可以通过设计一个函数来计算 4 元组下一次迭代的状态,从而节省开支。然后序列生成器函数 Seq.unfold
可用于构建包含每个状态四元组的第一个元素的序列,这是一种“惰性”操作——序列的元素仅在被消耗时按需计算.
let tetranacci (a3, a2, a1, a0) = a2, a1, a0, a3 + a2 + a1 + a0
(0, 1, 1, 2)
|> Seq.unfold (fun (a3, _, _, _ as a30) -> Some(a3, tetranacci a30))
|> Seq.take 10
|> Seq.toList
// val it : int list = [0; 1; 1; 2; 4; 8; 15; 29; 56; 108]
请注意,标准 Tetranacci 序列 (OEIS A000078) 通常会以 (0, 0, 0, 1)
:
// val it : int list = [0; 0; 0; 1; 1; 2; 4; 8; 15; 29]
kaefer's回答的不错,但是为什么停在线性时间?事实证明,您实际上可以实现对数时间,注意递归可以表示为矩阵乘法:
[T_n+1] [0; 1; 0; 0][T_n]
[T_n+2] = [0; 0; 1; 0][T_n+1]
[T_n+3] [0; 0; 0; 1][T_n+2]
[T_n+4] [1; 1; 1; 1][T_n+3]
但是然后T_n
可以通过循环n次来实现,我们可以看到M^n*[T_0; T_1; T_2; T_3]
的第一个条目(就是M^n
的右上角条目) ),我们可以通过重复平方在 O(log n) 时间内执行矩阵乘法:
type Mat =
| Mat of bigint[][]
static member (*)(Mat arr1, Mat arr2) =
Array.init arr1.Length (fun i -> Array.init arr2.[0].Length (fun j -> Array.sum [| for k in 0 .. arr2.Length - 1 -> arr1.[i].[k]*arr2.[k].[j] |]))
|> Mat
static member Pow(m, n) =
match n with
| 0 ->
let (Mat arr) = m
Array.init arr.Length (fun i -> Array.init arr.Length (fun j -> if i = j then 1I else 0I))
|> Mat
| 1 -> m
| _ ->
let m2 = m ** (n/2)
if n % 2 = 0 then m2 * m2
else m2 * m2 * m
let tetr =
let m = Mat [| [|0I; 1I; 0I; 0I|]
[|0I; 0I; 1I; 0I|]
[|0I; 0I; 0I; 1I|]
[|1I; 1I; 1I; 1I|]|]
fun n ->
let (Mat m') = m ** n
m'.[0].[3]
for i in 0 .. 50 do
printfn "%A" (tetr i)
这是一个 tail recursive 版本,主要编译为循环(其复杂度应为 O(n)):
let tetr n =
let rec t acc4 acc3 acc2 acc1 = function
| n when n = 0 -> acc4
| n when n = 1 -> acc3
| n when n = 2 -> acc2
| n when n = 3 -> acc1
| n -> t acc3 acc2 acc1 (acc1 + acc2 + acc3 + acc4) (n - 1)
t 0 1 1 2 n
acc1
对应tetra (n - 1)
,
acc2
对应tetra (n - 2)
,
acc3
对应tetra (n - 3)
,
acc4
对应tetra (n - 4)