Поиск ближайших чисел Фибоначчи

Я пытаюсь решить большую проблему, и я думаю, что важная часть программы тратится на неэффективные вычисления.

Мне нужно вычислить для данного числа N интервал [P, Q], где P-наибольшее число Фибоначчи, которое = N.

В настоящее время я использую карту для записи значения чисел Фибоначчи. Запрос обычно включает в себя поиск всех чисел Фибоначчи до n, и это не очень эффективно по времени, так как предполагает большое количество сравнений.

этот тип запросов будет происходить довольно часто в моей программе, и меня интересуют способы, которыми я мог бы улучшить поиск, предпочтительно с сублинейной сложностью.

10 ответов


числа Фибоначчи задаются формулой Бине

F(n) = ( phi^n - (1-phi)^n ) / \sqrt{5}

здесь phi золотое сечение,

phi = (1 + \sqrt{5}) / 2. 

это может быть реализовано прямолинейно (пример Python):

<<fibonacci_binet.py>>=
phi = (1 + 5**0.5) / 2

def fib(n):
    return int(round((phi**n - (1-phi)**n) / 5**0.5))

из-за ошибок округления с плавающей запятой это, однако, даст только правильный результат для n < 70.

формула Бине может быть перевернута, игнорируя (1-phi)^n термин, который исчезает при больших n. Поэтому мы можем определить обратная функция Фибоначчи, которая при заданном F(n) возвращает n (игнорируя, что F(1) = F(2)):

<<fibonacci_binet.py>>=
from math import log

def fibinv(f):
    if f < 2:
        return f
    return int(round(log(f * 5**0.5) / log(phi)))

здесь округление используется в наших интересах: оно устраняет ошибку, введенную нашей модификацией формулы Бине. Функция фактически возвращает правильный ответ при задании любого числа Фибоначчи, которое может быть сохранено как точное целое число в памяти компьютера. С другой стороны, он не проверяет, что данное число на самом деле является числом Фибоначчи; ввод большого числа Числа Фибоначчи или близкие к нему даст тот же результат. Поэтому вы можете использовать эту идею, чтобы найти число Фибоначчи, ближайшее к заданному числу.

идея, то это применить обратную карту Фибоначчи, чтобы найти N и M, два ближайших числа Фибоначчи с обеих сторон, затем используйте прямую карту Фибоначчи для вычисления P = F(N) и Q = F(M). Это требует больше вычислений, но меньше поиска.


я опубликовал полное доказательство концепции реализации этого на https://ideone.com/H6SAd

  • это невероятно быстро
  • он использует двоичный поиск adhoc
  • редактировать после прочтения других ответов у меня есть ощущение, что математические идеи, изложенные там (PengOne), приведут к более быстрому поиску (в основном: расчет перевернутой формулы плюс floor()/ceil() позвонить?)

.

#include <cmath>
#include <iostream>

const double pheta = 0.5*(std::sqrt(5)+1);

double fib(unsigned int n)
{
    return (std::pow(pheta, n) - std::pow(1 - pheta, n)) / std::sqrt(5);
}

unsigned int fibo_lowerbound(double N, unsigned min=0, unsigned max=1000)
{
    unsigned newpivot = (min+max)/2;
    if (min==newpivot)
        return newpivot;

    if (fib(newpivot) <= N)
        return fibo_lowerbound(N, newpivot, max);
    else
        return fibo_lowerbound(N, min, newpivot);
}

std::pair<double, double> fibo_range(unsigned int n)
{
    unsigned int lbound = fibo_lowerbound(n);
    return std::make_pair(fib(lbound), fib(lbound+1));
}

void display(unsigned int n)
{
    std::pair<double, double> range = fibo_range(n);
    std::cout << "Fibonacci range wrapping " << n << " is "
              << "[" << (unsigned long long) range.first << ", " << (unsigned long long) range.second << "]"
              << std::endl;
}

int main()
{
    display(1044);
    display(8999913);
    display(7);
    display(67);
}

выход:

Fibonacci range wrapping 1044 is [987, 1597]
Fibonacci range wrapping 8999913 is [5702887, 9227465]
Fibonacci range wrapping 7 is [5, 8]
Fibonacci range wrapping 67 is [55, 89]

можно использовать выражение закрытой формы чисел Фибоначчи.

Так как второй член в нем очень мал, вы можете приблизить его только с первым членом, так что n можно найти с логарифмом золотого сечения.


используйте формулу замкнутой формы:http://en.wikipedia.org/wiki/Fibonacci_number#Closed-form_expression

тогда двоичный поиск


Я только что сделал головоломку CodeChef, которая была этой точной проблемой (http://www.codechef.com/problems/DPC204). Я просто вычислил последовательность Фибоначчи от 0 до конца диапазона и подсчитал, сколько было после начала диапазона. Мой тест для любых их входов выборки был принят 2.6 M и 0.00 s, поэтому решение nieve достаточно быстро.

в принципе, я сделал класс big-unsigned-int из unsigned int[333], и вычислить два числа на цикл, чтобы избежать свопов.

start with A=0,B=1;
A+=B;B+=A; 
now A==1,B==2, the next two Fib. numbers, with no swaps.
A+=B;B+=A; 
now A==3,B==5, the next two Fib. numbers, with no swaps.

это немного осложняется тем, что вам нужно остановиться и проверить, находятся ли ни одно, ни одно или оба числа в диапазоне, но A

мое решение на CodeChef синхронизировано в 0.00 секунд, поэтому я думаю, что этот метод должен быть достаточно быстрым, вам просто нужно написать функцию, которая добавляет один uint[333] в другой uint[333] (используя все 32 бита, просто символы для каждой десятичной цифры)


поскольку вы рассматриваете только 64-битные целые числа, необходимо рассмотреть не более 100 чисел Фибоначчи. Вы можете предварительно вычислить их, используя их определение Fn = Fn-1 + Fn-2.

затем предварительно вычислить другую таблицу, которая отображает количество ведущих нулевых битов в индекс в таблице чисел Фибоначчи, на первое число с таким количеством ведущих нулевых битов.

теперь, чтобы найти интервал, используйте количество ведущих нулевых битов ваш номер (это можно вычислить быстро, так как многие процессоры имеют специальную инструкцию для него), чтобы найти начальную точку, используя вторую таблицу, и линейно искать через первую таблицу для интервала. Поскольку существует не более двух чисел Фибоначчи между смежными степенями двух, это занимает не более 2 шагов.

Это имеет то преимущество, что он использует только целочисленную арифметику, которая является точной и имеет тенденцию быть быстрее, чем с плавающей точкой вычислений.


используя последнюю форму здесь для обратного, вы можете найти два индекса для чисел Fib вокруг текущего числа. http://en.wikipedia.org/wiki/Fibonacci_number#Computation_by_rounding

log(N * sqrt(5)) / log((1+sqrt(5))/2) должно дать вам число, которое находится между двумя целочисленными индексами для P и Q. Затем вы можете использовать закрытую форму (как показано в других ответах), чтобы дать фактические числа P и Q.

обратите внимание, что вы можете быть выключены на один в зависимости от ваших начальных условий Fib.


Я думаю, что важная часть программы тратится на неэффективные вычисления.

вы профилированный ваш код? В качестве общего принципа не оптимизируйте преждевременно; измерьте, какие части замедляют его. Таким образом, когда вы пытаетесь оптимизировать, вы можете сказать, помогли ли оптимизации или повредили (часто хорошая оптимизация сделает ее хуже; как говорят, компилятор не сможет выполнить свои оптимизации или вы не сможете для оптимального использования регистров/кэша вашего процессора).

если это то, что замедляет вас, я бы сделал аналогично великому решению Пэнга, но предварительно вычисляя все числа Fib до вашего наибольшего значения и храню их в массиве, индексированном соответствующим expoential (n) из закрытой формы (phi^**n - (1-phi)**n)/sqrt(5). Его метод будет неправильно вычислять числа Fib для больших n с арифметикой с плавающей запятой; если вы не используете произвольную высокую точность (которая медленная). Таким образом, ваш начальный массив fib_array = [0,1,1,2,3,5,8,13,... ]. Тогда пренебрегая малым (1-phi)**n term, инвертировать fib, чтобы найти n (например, Peng's fib_inv), и забрать fib_array[n] как ваша первая граница. Если эта граница меньше (больше), чем ваше значение; вы нашли нижнюю (верхнюю) границу, и поэтому другая граница должна быть fib_array[n+1] (fib_array[n-1]).
Или, если вы хотите вычислить его, используйте что-то из данного N, что лучше, чем формула Бине. http://en.literateprograms.org/Fibonacci_numbers_%28Python%29

лично я бы проверил убедитесь, что вторая граница находится на противоположной стороне термина как первая граница (в редких случаях, когда мы не должны были пренебрегать (1-phi)**n term; вы могли бы сделать другой поиск, видя, ограничен ли термин, например,fib_array[n+1] и fib_array[n+2]). (Этот чек может быть излишним, но сначала вам нужно доказать это, и одно дополнительное сравнение, чтобы быть безопасным, кажется, стоит в моей книге).


создайте таблицу чисел Фибоначчи, которая поместится в 8 байтов; есть только 94. Это избавит вас от необходимости вычислять их на каждой итерации. Здесь нет необходимости в математике с плавающей запятой.

затем используйте двоичный поиск, чтобы найти число ниже и выше вашего номера за это время. Это избавит вас от сравнения всех чисел и сократит ваш поиск до постоянного времени поиска.

Это соответствует вашим требованиям, но обратите внимание, что ваши требования не указывают, что должно возвращено для N таким образом, что нет Q в 64-битном целочисленном пространстве, т. е. N > 12,200,160,415,121,876,738. Если вас это волнует, решите, как вы хотите справиться с этим. :)

#include "stdint.h"
#include "stdio.h"
#include "stdlib.h"
#include "time.h"

/* build a table of all fibonacci numbers that fit in a uint64_t. */
static const int fibonacciCount = 94;
uint64_t fibonacciSequence[fibonacciCount];
static void precalc(void) {
    fibonacciSequence[0] = 0;
    fibonacciSequence[1] = 1;
    for (int i = 2; i < fibonacciCount; ++i) {
        fibonacciSequence[i] = fibonacciSequence[i-2] + fibonacciSequence[i-1];
    }
}

/* do a binary search for the Fibonacci numbers >= N and <= N */
static void find_closest_fibonacci(uint64_t N, uint64_t *P, uint64_t *Q) {
    int upper = fibonacciCount;
    int lower = 0;
    do {
        int mid = ((upper - lower) >> 1) + lower;
        uint64_t midValue = fibonacciSequence[mid];
        if ( midValue > N ) {
            upper = mid;
        } else if ( midValue < N ) {
            lower = mid + 1;
        } else {
            *P = fibonacciSequence[ mid ];
            *Q = fibonacciSequence[ mid ];
            return;
        }
    } while ( upper > lower );
    *P = fibonacciSequence[ lower - 1 ];
    *Q = fibonacciSequence[ lower ];
}

/* hacked together 64 bit random number generator,
 used just in tester only */
static uint64_t rand64(void) {
    /* totally flawed as a random number generator,
     but that's not the point here. */
    uint64_t v = 0;
    for (int i = 0; i < 8; ++i) {
        v = (v << 8) + (rand() % 256);
    }
    return v;
}

int main (int argc, const char * argv[]) {
    srand( (unsigned)time( NULL ) );

    precalc(); /* do this once only */

    uint64_t upperBound = fibonacciSequence[fibonacciCount - 1];
    printf( "Upper bound is %qu\n", upperBound );

    /* build a sample to run against the algorithm
     we favor mostly numbers below RAND_MAX, because
     if we test across all of UINT64_MAX the results are
     pretty boring. */
    static const int sampleCount = 100;
    static const int normalSampleCount = 90;
    uint64_t numbers[sampleCount];
    for (int i = 0; i < normalSampleCount; ++i) {
        numbers[i] = rand();
    }
    for (int i = normalSampleCount; i < sampleCount; ++i) {
        uint64_t number;
        do {
            number = rand64();
        } while ( number > upperBound );
        numbers[i] = number;
    }

    /* use described algorithm */
    for (int i = 0; i < 100; ++i) {
        uint64_t P;
        uint64_t Q;
        uint64_t N = numbers[i];
        find_closest_fibonacci(N, &P, &Q);
        printf( "%qu [%qu,%qu]\n", N, P, Q );
    }

    return 0;
}

поместите любой другой алгоритм в тот же файл и запустите его против того же тестера.


число Фибоначчи тогда и только тогда, когда один или оба из (5*n^2 + 4) или (5*n^2 – 4) является идеальным квадратом. Я использую эту предпосылку, чтобы проверить, принадлежит ли входное число ряду Фибоначчи или нет.

#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>

typedef struct node{

    int64_t value;
    struct node *next;

}Node;

Node *head ;

void readElements(int);
int isPerfectSquare(int64_t sqrValue);

int main(){

    int input_count , flag=0;
    Node *temp_node = NULL;
    int64_t sqrValue = 0;

    scanf("%d" , &input_count);

    if((input_count < 1 )||(input_count > 100000)){
        printf("Total number of Inputs out of Range ..!!\n");
        return 1;
    }

    readElements(input_count);

    /*Reading the elements from the list*/

    temp_node = head;

    while(temp_node != NULL){

        sqrValue = 5*pow(temp_node->value , 2);
        flag = (isPerfectSquare(sqrValue+4) || isPerfectSquare(sqrValue-4));

        if(flag == 1){
            printf("IsFibo\n");
        }
        else{
            printf("IsNotFibo\n");
        }

        temp_node = temp_node->next;

    }   



    return 0;

}


void readElements(int input_count){

    int temp = 0;
    int64_t val = 0;
    Node *temp_node =NULL , *cur = NULL;
    char b[20];


    while (temp < input_count) {

        scanf("%s" , b);
        val = atol(b);

        if(val < 0 || val >10000000000)
            continue;

        temp_node = (Node*) malloc(sizeof(Node));

        temp_node->value = val;
        temp_node->next = NULL;

        if(head == NULL){
            head = cur = temp_node;
        }
        else{
            cur->next = temp_node;
            cur = temp_node;
        }

        temp++;

    }

}

int isPerfectSquare(int64_t sqrValue){

    int64_t s = 0;

    s = sqrt(sqrValue);

    return(s*s == sqrValue);

}