问题
I am trying to solve a bigger problem, and I think that an important part of the program is spent on inefficient computations.
I need to compute for a given number N, the interval [P, Q], where P is the biggest fibonacci number that is <= to N, and Q is the smallest fibonacci number that is >= to N.
Currently, I am using a map to record the value of the fibonacci numbers. A query normally involves searching all the fibonacci numbers up to N, and it is not very time efficient, as it involves a big number of comparisons.
This type of queries will occur quite often in my program, and I am interested in ways that I could improve the lookup, preferably with sub-linear complexity.
回答1:
The Fibonacci numbers are given by Binet's formula
F(n) = ( phi^n - (1-phi)^n ) / \sqrt{5}
where phi
is the golden ratio,
phi = (1 + \sqrt{5}) / 2.
This can be implemented straightforwardly (Python example):
<<fibonacci_binet.py>>=
phi = (1 + 5**0.5) / 2
def fib(n):
return int(round((phi**n - (1-phi)**n) / 5**0.5))
Because of floating-point rounding errors, this will however only give the right result for n < 70
.
Binet's formula can be inverted by ignoring the (1-phi)^n
term, which disappears for large n
. We can therefore define the inverse Fibonacci function that, when given F(n)
, returns n
(ignoring that 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)))
Here rounding is used to our advantage: it removes the error introduced by our modification to Binet's formula. The function will in fact return the right answer when given any Fibonacci number that can be stored as an exact integer in the computer's memory. On the other hand, it does not verify that the given number actually is a Fibonacci number; inputting a large Fibonacci number or any number close to it will give the same result. Therefore you can use this idea to find the Fibonacci number closest to a given number.
The idea, then is to apply the inverse Fibonacci map to find N
and M
, the two closest Fibonacci numbers on either side, then use the direct Fibonacci map to compute P = F(N)
and Q = F(M)
. This involves more computation, but less searching.
回答2:
I posted a complete Proof-Of-Concept implementation of this on https://ideone.com/H6SAd
- it is blazingly fast
- it uses an adhoc binary search
- Edit after reading the other responses, I have a feeling that mathematical ideas outlined there (PengOne) will lead to a quicker lookup (basically: a calculation of the inverted formula plus a floor()/ceil() call?)
.
#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);
}
The output is:
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]
回答3:
You can use the closed-form expression of the fibonacci numbers.
Since the second term in it is very small, you can approximate it with just the first term, so n
can be found with base-golden ratio logarithm.
回答4:
Use the closed form formula: http://en.wikipedia.org/wiki/Fibonacci_number#Closed-form_expression
Then binary search
回答5:
I just did a CodeChef puzzle that was this exact problem (http://www.codechef.com/problems/DPC204). I simply calculated the Fibonacci sequence from 0 to the end of the range, and counted how many were after the beginning of the range. My test for whatever their sample inputs were took 2.6M, and 0.00s, so the nieve solution is plenty fast enough.
Basically, I made a big-unsigned-int class made of unsigned int[333]
, and calculate two numbers per loop, to avoid swaps.
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.
It is slightly complicated by the fact you have to stop and check if the neither, one, or both numbers are in the range, but A
My solution on CodeChef clocked in at 0.00 seconds, so I think this method ought to be fast enough, you just have to write a function that adds one uint[333]
to another uint[333]
(using all 32 bits, just chars for each decimal digit)
回答6:
Since you consider only 64 bit integers, there are at most about 100 Fibonacci numbers to consider. You can precompute them using their definition Fn = Fn-1 + Fn-2.
Then precompute another table that maps the number of leading zero bits to an index in the table of Fibonacci numbers, to the first number with that many leading zero bits.
Now to find the interval use the number of leading zero bits of your number (this can be computed quickly as many processors have a special instruction for it) to find a starting point using the second table, and linearly search through the first table for the interval. Since there are at most two Fibonacci numbers between adjacent powers of two this takes at most 2 steps.
This has the advantage that it only uses integer arithmetic, which is exact and tends to be faster than floating point computations.
回答7:
Using the last form here for the inverse you can find the two indexes for the Fib numbers around the current number. http://en.wikipedia.org/wiki/Fibonacci_number#Computation_by_rounding
log(N * sqrt(5)) / log((1+sqrt(5))/2)
should give you a number which is between the two integer indexes for P
and Q
. You can then used the closed-form (as shown in the other answers) to give the actual numbers P
and Q
.
Note that you may be off by one depending on your initial Fib conditions.
回答8:
I think that an important part of the program is spent on inefficient computations.
Have you profiled your code? As a general principle don't prematurely optimize; measure what parts are slowing it down. That way when you try optimizing, you can tell if the optimizations helped or hurt (often a sounds-good optimization will make it run worse; as say the compiler will not be able to do its optimizations or you aren't able to use your cpu's registers/cache as optimally).
If this is what's slowing you down, I would do similar to Peng's great solution but pre-computing all the Fib numbers up to your largest value and store them into an array indexed by the corresponding expoential (n) from the closed-form (phi^**n - (1-phi)**n)/sqrt(5)
. His method will miscompute Fib numbers for large n with floating point arithmetic; unless you use arbitrary high-precision (which is slow). So your starting array is fib_array = [0,1,1,2,3,5,8,13,... ]
. Then neglecting the small (1-phi)**n
term, invert fib to find n (e.g., Peng's fib_inv
), and take fib_array[n]
as your first bound. If this bound is smaller (larger) than your value; you've found the lower (upper) bound, and so the other bound should be fib_array[n+1]
(fib_array[n-1]
).
Or if you want to compute it use something from given a N that is better than Binet's formula. http://en.literateprograms.org/Fibonacci_numbers_%28Python%29
Personally, I'd check to make sure that the second bound is on the opposite side of the term as the first bound (in rare cases where we shouldn't have neglected the (1-phi)**n
term; you could possibly have do to another lookup seeing if the term is bounded by e.g., fib_array[n+1]
and fib_array[n+2]
). (This check may be redundant; but you'd have to prove that first, and the one extra comparison to be safe seems worth it in my book).
回答9:
Build a table of Fibonacci numbers that will fit in 8 bytes; there's only 94. That will save you calculating them through each iteration. There's no need for floating point math here.
Then use a binary search to find the number below and above your number in the time. That will save you comparing all the numbers, and reduce your search to a constant search time.
This meets your requirements, but note that your requirements do not specify what should be returned for N such that there is no Q in 64 bit integer space, i.e. N > 12,200,160,415,121,876,738. If you care about that, decide how you want to handle it. :)
#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;
}
Put whatever other algorithm you have in the same file, and run it against the same tester.
回答10:
On Scala find the closet Fibonaci number looks also very simple:
val phi = (1 + sqrt(5))/2
def fib(n: Long): Long = round( ( pow(phi,n) - pow((1-phi),n) ) / sqrt(5) )
def fibinv(f: Long): Long = if (f < 2) f else round( log(f * sqrt(5) ) /log(phi))
回答11:
A number is Fibonacci if and only if one or both of (5*n^2 + 4) or (5*n^2 – 4) is a perfect square. I am using this premise to verify if the input number belongs to a fibonacci series or not.
#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);
}
来源:https://stackoverflow.com/questions/7843048/finding-the-closest-fibonacci-numbers