【问题标题】:Finding the closest fibonacci numbers找到最接近的斐波那契数
【发布时间】:2011-10-20 22:31:44
【问题描述】:

我正在尝试解决一个更大的问题,我认为程序的一个重要部分用于低效的计算。

我需要计算给定数 N 的区间 [P, Q],其中 P 是 = 到 N 的最小斐波那契数。

目前,我正在使用地图来记录斐波那契数的值。 查询通常涉及搜索最多 N 个斐波那契数,而且时间效率不高,因为它涉及大量比较。

这种类型的查询在我的程序中经常出现,我对改进查找的方法很感兴趣,最好是亚线性复杂度。

【问题讨论】:

  • 如何使用快速排序或冒泡排序来减少比较次数?
  • 这个有多大?斐波那契数呈指数增长,因此我可以想象您可以将所有这些数字预先计算到最大大小并对该列表进行二进制搜索。
  • 了解 N 的大小对于提出一个好的解决方案至关重要。如果 N 可以为 1e100 或更大,则涉及浮点计算的解决方案可能是合理的;如果它仅限于 32 位整数,那么这样的计算将是多余的。
  • N 最多可以表示为一个 8 字节的整数。

标签: c++ algorithm math complexity-theory discrete-mathematics


【解决方案1】:

斐波那契数由比内公式给出

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 &lt; 70 提供正确的结果。

Binet 的公式可以通过忽略 (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)))

这里使用舍入对我们有利:它消除了我们对比内公式的修改引入的错误。当给定任何可以作为精确整数存储在计算机内存中的斐波那契数时,该函数实际上将返回正确答案。另一方面,它不会验证给定的数字是否真的是斐波那契数;输入一个大的斐波那契数或任何接近它的数将给出相同的结果。因此,您可以使用这个想法来找到最接近给定数字的斐波那契数。

然后的想法是应用逆斐波那契图来找到NM,这两个最接近的斐波那契数在两边,然后使用直接斐波那契图来计算P = F(N)Q = F(M)。这涉及更多的计算,但更少的搜索。

【讨论】:

  • +1 来自我。再说一次,我很羡慕更扎实的数学背景
  • @PengOne:感谢这个非常有用的答案!为了好玩,我尝试在 Ruby (gist.github.com/IRLeif/40c543a5237a6fce587c) 中实现它,但我仍在尝试理解您答案的最后两段,以便将它们放在一起。
【解决方案2】:

我在 https://ideone.com/H6SAd

上发布了一个完整的概念验证实现
  • 速度极快
  • 它使用临时二分搜索
  • 编辑在阅读了其他回复后,我觉得那里概述的数学思想 (PengOne) 将导致更快的查找(基本上:计算倒置公式加一个地板()/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]

【讨论】:

  • 不要被更多数学答案吓倒。由于 OP 只对 64 位以下的整数感兴趣,因此她只需要预先计算大约 93 个斐波那契数,而二进制搜索最多涉及 7 个整数比较。这几乎肯定比计算单个对数还要快。为您的回答 +1。
  • 同意,Java 版本在我的 2.6 ghz 机器上提供了大约 400 万次搜索/秒。
【解决方案3】:

您可以使用斐波那契数的closed-form expression

由于其中的第二项很小,你可以只用第一项来近似,所以n可以用底黄金比对数求出。

【讨论】:

    【解决方案4】:

    【讨论】:

    • 这是我实现的。我确实有一种感觉,更倾向于数学的响应(PengOne/mange)可能比这里的二进制搜索更合适!
    【解决方案5】:

    我刚刚做了一个 CodeChef 谜题,就是这个问题 (http://www.codechef.com/problems/DPC204)。我只是简单地计算了从 0 到范围末尾的斐波那契数列,并计算了在范围开始之后有多少个。我对他们的样本输入进行了 2.6M 和 0.00 秒的测试,因此 nieve 解决方案足够快。

    基本上,我创建了一个由 unsigned int[333] 组成的 big-unsigned-int 类,并在每个循环中计算两个数字,以避免交换。

    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 位,仅字符每个十进制数字)

    【讨论】:

    • 我刚刚测试了我的。它在我的机器上找到 0.00428814s 冷、0.00222952s 热的 0 到 1e100(含)之间的所有斐波那契数。可能没有其他建议那么快,但对于任何目的来说都足够快。
    【解决方案6】:

    由于您只考虑 64 位整数,因此最多需要考虑 100 个斐波那契数。您可以使用它们的定义 Fn = Fn-1 + Fn-2 预先计算它们。

    然后预先计算另一个表,将前导零位的数量映射到斐波那契数表中的索引,映射到具有那么多前导零位的第一个数字。

    现在要找到区间,请使用数字的前导零位数(这可以快速计算,因为许多处理器都有专门的指令)使用第二个表找到起点,然后线性搜索第一个间隔表。由于在 2 的相邻幂之间最多有两个斐波那契数,因此最多需要 2 步。

    它的优点是它只使用整数运算,它是精确的并且往往比浮点计算更快。

    【讨论】:

      【解决方案7】:

      使用此处的最后一种形式进行逆运算,您可以找到当前数字周围的 Fib 数字的两个索引。 http://en.wikipedia.org/wiki/Fibonacci_number#Computation_by_rounding

      log(N * sqrt(5)) / log((1+sqrt(5))/2) 应该给你一个介于PQ 的两个整数索引之间的数字。然后,您可以使用封闭形式(如其他答案所示)给出实际数字 PQ

      请注意,根据您的初始 Fib 条件,您可能会偏离 1。

      【讨论】:

        【解决方案8】:

        我认为程序的一个重要部分都花在了低效的计算上。

        你有profiled你的代码吗?作为一般原则,不要过早优化;测量哪些部分正在减慢速度。这样,当您尝试优化时,您可以判断优化是帮助还是伤害(通常听起来不错的优化会使它运行得更糟;例如编译器将无法进行优化,或者您无法使用您的cpu 的寄存器/缓存为最佳)。

        如果这是让你慢下来的原因,我会做类似于 Peng 的伟大解决方案,但预先计算所有 Fib 数字直到你的最大值,并将它们存储到一个数组中,该数组由封闭的相应指数 (n) 索引-形式(phi^**n - (1-phi)**n)/sqrt(5)。他的方法会用浮点运算错误计算大 n 的 Fib 数;除非你使用任意高精度(这很慢)。所以你的起始数组是 fib_array = [0,1,1,2,3,5,8,13,... ]。然后忽略小的 (1-phi)**n 术语,反转 fib 以找到 n(例如,彭的 fib_inv),并将 fib_array[n] 作为您的第一个界限。如果此界限小于(大于)您的值;您找到了下(上)界限,等等另一个界限应该是fib_array[n+1] (fib_array[n-1])。
        或者,如果您想计算它,请使用给定 N 中比 Binet 公式更好的东西。 http://en.literateprograms.org/Fibonacci_numbers_%28Python%29

        就个人而言,我会检查以确保第二个界限与第一个界限在术语的相反侧(在极少数情况下,我们不应该忽略 (1-phi)**n 术语;您可能会这样做到另一个查找,看看该术语是否受例如fib_array[n+1]fib_array[n+2]) 限制。 (此检查可能是多余的;但您必须首先证明这一点,并且在我的书中,为了安全而进行的额外比较似乎值得)。

        【讨论】:

          【解决方案9】:

          建立一个包含 8 个字节的斐波那契数列;只有 94 个。这样可以节省您在每次迭代中计算它们的时间。这里不需要浮点数学。

          然后使用二分法查找时间中你的数字下方和上方的数字。这将节省您比较所有数字的时间,并将您的搜索减少到恒定的搜索时间。

          这符合您的要求,但请注意,您的要求并未指定应为 N 返回的内容,因此 64 位整数空间中没有 Q,即 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;
          }
          

          将您拥有的任何其他算法放在同一个文件中,然后针对同一个测试器运行它。

          【讨论】:

            【解决方案10】:

            在Scala上找到壁橱斐波那契数看起来也很简单:

            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】:

            一个数是斐波那契当且仅当 (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);
            
            }
            

            【讨论】:

            猜你喜欢
            • 2017-04-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-11-28
            • 2015-06-05
            • 2014-05-23
            • 2014-05-03
            相关资源
            最近更新 更多