Более эффективная рекурсивная функция Tetranacci в F # - PullRequest
2 голосов
/ 05 марта 2020

Я пытаюсь написать функцию tetranacci, используя F # настолько эффективно, насколько это возможно, первое решение, которое я нашел, было действительно неэффективным. Вы можете помочь мне придумать лучший вариант? Как я смогу реализовать это за линейное время?

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)

Ответы [ 3 ]

5 голосов
/ 06 марта 2020

ответ Кафер хорош, но зачем останавливаться на линейном времени? Оказывается, что вы можете вместо этого достичь логарифмического c времени, отметив, что повторение можно выразить как умножение матрицы:

[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)
5 голосов
/ 06 марта 2020

Вы могли бы сэкономить, разработав функцию, которая вычисляет состояние для следующей итерации на 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]
1 голос
/ 08 марта 2020

Вот версия с хвостовой рекурсией , которая компилируется в основном в циклы (и ее сложность должна быть 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)

На основании примера Фибоначчи

...