递归的最佳解决方案

时间:2018-10-12 12:52:43

标签: c++ algorithm data-structures

几天前我编码了一个DP问题,我想将递归解决方案减少为更优化的解决方案,即一次又一次减少同一函数的多次调用

n <10 ^ 9和k <10 ^ 3

long long fibo(long long n,long long k){
    if(n==k){
       return 2;
    }
    else if(k==1){
       return 2*n; 
    }
    else if(k==0){
       return 1;
    }
    else if(k==2){
       return 2*(n-1)*(n-1);
    }
    else {
       return (fibo(n-1,k) + fibo(n-2,k-1) + fibo(n-1,k-1));
    }
}

迭代方法

long long fibo[1010][1010]={-1};
long long fib(long long n,long long k){
     if(n==k){
       return 2;
      }
    else if(k==1){
      return 2*n;
      }

    else if(k==0){
      return 0;
     }
    else if(k==2){
      return 2*(n-1)*(n-1);
     }

   if(fibo[n][k]!=-1){
       return fibo[n][k];
      }
  else {
      return  fibo[n][k]= (fib(n-1,k) + fib(n-2,k-1) + fib(n-1,k-1));
      }
 }

1 个答案:

答案 0 :(得分:3)

编辑:我添加了性能测试的结果和fibo(n, k)的粗略数学估计。


我的答案

  

我想将递归解决方案简化为更优化的解决方案

以下代码是我的答案。 该代码由动态编程实现。 该算法的细节在下面说明。 与原始代码的匹配测试为here,而性能测试为here。性能得到了很好的改善。 fibo(40, 10)的计算时间如下:

  

原始:24691879微秒(〜24秒)

     

DP。版本:33微秒

long long fibo_DP(long long n, long long k)
{
    if(k<0 || n<k){
        throw std::logic_error("Value of fibo is ill-defined for (k<0 || n<k).");
    }

    // boundary
    if(n==k){
        return 2;
    }
    else if(k==1){
        return 2*n; 
    }
    else if(k==0){
        return 1;
    }
    else if(k==2){
        return 2*(n-1)*(n-1);
    }

    std::vector<long long> memo(k+2, 2); // diagonal line.

    const auto n_last = (n-k);
    for(auto i = 1U; i<=n_last; ++i)
    {
        // modifying first two values
        memo[0] = 1;       // (i  ,0)
        memo[1] = 2*(i+1); // (i+1,1)

        // throwing away the value of (k, k) by sliding
        for(auto l = memo.size()-2; l>3U; --l){
            memo[l] = memo[l-1];
        }

        // interrupting the value of (i+2, 2)
        memo[3] = 2*(i+1)*(i+1);

        // do local updates.
        for(auto j = 4U; j<memo.size(); ++j)
        {
            const auto sum = memo[j-2] + memo[j-1] + memo[j];

            memo[j-2] = memo[j-1];
            memo[j-1] = memo[j];
            memo[j]   = sum;
        }
    }

    return memo.back();    
}

我们问题的明确定义

首先,我认为只有并且k>=0 && n>=k时,这个问题才是明确定义的。 为了明确起见,请考虑2维矩阵,其中(n, k)处的值等于fibo(n, k)。 然后是您的顺序总和

return (fibo(n-1,k) + fibo(n-2,k-1) + fibo(n-1,k-1));

由下图表示:

summation

要终止该和,我们需要一个上边界和一个左边界,其中给出初始值。 OTOH,仅为n==kk==0, 1, 2,下图上的蓝色框定义了初始值:

regions

如果(n, k)位于n<k的区域(1)上,则由于没有给出上限,上述顺序求和将成为无限循环。 并且,如果(n, k)位于k<0 && n>=k的区域(2)上,则由于没有给出左边界,它再次成为无限循环。 因此,当且仅当fibo(n, k)并且 我们在下面的讨论中假设k>=0 && n>=k


此算法的详细信息

回到第一个求和图,我们可以在计算k>=0 && n>=k时找到以下算法。 首先,我们构造一个包含fibo(n, k)元素(1,0),(2,1),(2,2)(3,2)的数组>,(3,3),(4,4),(5,5)…(k,k),下图上的黑框:

initial state

接下来,我们通过以下方式开始递归求和。 在每个步骤中,黑盒还是k+2元素,它们存储在数组中。 此过程是本地过程,从内存访问的角度来看将是有效的:

update

如果我们已经计算出(k + 1,k),则我们得到的值为(1,0),(2,1),(3,2)(4 ,3)(5,4)(6,5),...,(k,k-1) ,(k,k),(k + 1,k)。 然后我们转到下一条对角线平行线。 修改数组的前两个值,在第四个元素处中断(4,2)的值,丢弃(k,k)的值,我们构造下一个初始数组(2,0)(3、1),(3、2),(4、2),(4、3),(5、4),...,( k-1,k-2),(k,k-1),(k + 1,k)。

next

我们再次以以前的方式开始顺序求和。

重复此过程,最后得到k+2的值。


数学和粗略估计

对于fibo(n, k)fibo(k+1, k),我们得到以下递归方程:

k+1_recursion

我们可以求解该方程式并得到以下结果:

k+1_formula

接下来,对于k>=3fibo(k+2, k),我们得到以下递归方程:

k+2_recursion

我们可以再次求解该方程式,并获得以下结果:

k+2_formula

通过这种方式,我们可以将k>=3的顺序估计为fibo(k+p,k), 即k^p不幸的是,fibo(n,k) ~ k^(n-k)对于n~10^9 and k~10^3来说太大了,将返回无意义的值。