Número de Fibonacci generalizado de Ne em O (log N)

No trabalho do curso, foi necessário escrever um algoritmo com complexidade logarítmica, que encontrará o enésimo número da sequência de Fibonacci.





Algoritmo

Encontrei vários artigos sobre esse assunto, todos tratando da clássica série de números de Fibonacci. Para ele, você pode aplicar esta fórmula:





Mas em meu trabalho, foram usadas séries generalizadas, nas quais os dois primeiros números são zero e algum parâmetro. Depois de horas de pesquisa, encontrei um comentário interessante no qual o autor fornece uma fórmula para a descoberta cíclica de qualquer sequência linear recorrente (incluindo uma série de números de Fibonacci).





Aqui, Q é uma matriz 2x2 cujos elementos podem ser facilmente calculados.





Substituindo alguns poucos números de Fibonacci, descobrimos que a matriz Q = [[0,1], [1,1]].





A fórmula final da matriz, que contém o enésimo número da série generalizada de Fibonacci, pode ser escrita da seguinte forma:





Fn é o número de Fibonacci desejado,

C é a chave,

n é o número ordinal do número





Obviamente, para a eficiência de todo o algoritmo, é necessário usar o algoritmo mais rápido para elevar a matriz Q à potência n. Este artigo me ajudou a lidar com isso. Isso explica que, para elevar uma matriz à potência de n, você pode dividir n em potências de dois e, em seguida, elevar as matrizes apenas a essas potências. Esta abordagem fornece a complexidade O (log N).





Então resta apenas multiplicar pela matriz [[C, C], [C, 0]] e obter o elemento com o índice [0,1].





Implementação Python

class FiboMatrix:
    key = 0
    matrix_cur = [[0,0], [0,0]]
    matrix_one = [[0,1], [1,1]]

    def __init__(self, key):
        self.key = key
        self.matrix_cur = [[key, key], [key, 0]]
        self.PowsBuffer = {}
        #       

    def MultiplyMatrix(self, M1, M2):
        """ 
          2x2   ."""

        a11 = M1[0][0] * M2[0][0] + M1[0][1] * M2[1][0]
        a12 = M1[0][0] * M2[0][1] + M1[0][1] * M2[1][1]
        a21 = M1[1][0] * M2[0][0] + M1[1][1] * M2[1][0]
        a22 = M1[1][0] * M2[0][1] + M1[1][1] * M2[1][1]
        return [[a11, a12], [a21, a22]]

    def PowMatrix(self, M: list, p: int):
        """   .
          2x2     .
        """

        if p == 1:
            return M
        if p in self.PowsBuffer:
            return self.PowsBuffer[p]

        K = self.PowMatrix(M, int(p / 2))
        R = self.MultiplyMatrix(K, K)
        self.PowsBuffer[p] = R
        return R

    def GetNum(self, n):
        if n == 0:
            return 0
        if n == 1:
            return 1
        
        #      n   
        powers = []
        p = 0
        for digit in reversed(bin(n)[2:]):
            if digit == '1':
                powers.append(int(pow(2, p)))
            p += 1

        matrices = [self.PowMatrix(FiboMatrix.matrix_one, p)
                    for p in powers]

        #     

        while len(matrices) > 1:
            M1 = matrices.pop()
            M2 = matrices.pop()
            R = self.MultiplyMatrix(M1, M2)
            #   
            matrices.append(R)

        self.matrix_cur = self.MultiplyMatrix(self.matrix_cur, matrices[0])
        #    F1  F2  ,    
        return self.matrix_cur[0][1]
      
      



Para comparar a eficiência, o análogo mais simples deste algoritmo foi escrito usando um loop:





def Fib(num, key):
    fib_1 = 0
    fib_2 = key

    for dec in range(num):
        fib_1, fib_2 = fib_2, fib_1+fib_2

    return fib_2
      
      



Os benchmarks confirmam nossas expectativas: o algoritmo clássico leva várias vezes mais tempo já no décimo número.








All Articles