Matlab递归:低效的代码还是复杂的递归?

Posted

技术标签:

【中文标题】Matlab递归:低效的代码还是复杂的递归?【英文标题】:Matlab recursion: inefficient code or complex recursion? 【发布时间】:2018-02-28 12:06:13 【问题描述】:

我正在努力在合理的执行时间内解决这个递归问题。

在这里,我展示了递归函数,它基本上计算多项式的系数。

function [ coeff ] = get_coeff( n, k, tau, x )

if(n == 0) % 1st exit condition
    coeff = 0;
else
    if(k == 0) % 2nd exit condition
        coeff = max(0, n*tau-x)^n;
    else % Else recursion
        total = 0;
        for l = k-1:n-2
            total = total + nchoosek(l, k-1)*tau^(l-k+1)*get_coeff(n-1, l, tau, x);
        end
        coeff = (n/k) * total;            
    end
end

end

 % This symbolic summation solution gives numerical errors, probably due to rounding
 % effects.
 %           syms l;
 %           f = nchoosek(l, k-1)*tau^(l-k+1)*get_coeff(n-1, l, tau, x);
 %           coeff = (n/k) * symsum(f, l, k-1, n-2);

这是我使用递归函数的主要脚本:

Tau = 1;
ns = [3];
%delays = 0:0.25:8;
delays = [0];
F_x = zeros(1, size(delays, 2));
rho = 0.95;
tic
for ns_index = 1: size(ns, 2)

  T = Tau*(ns(ns_index)+1)/rho;

  % Iterate delays (x)
  for delay_index = 1:size(delays, 2)
     total = 0;

     % Iterate polynomial.
     for l = 0:ns(ns_index)-1
        total = total + get_coeff(ns(ns_index), l, Tau, delays(delay_index))*(T - ns(ns_index)*Tau + delays(delay_index))^l;
     end

    F_x(1, delay_index) = T^(-ns(ns_index))*total;

  end

end
toc

我已将“ns”和“延迟”向量简化为包含单个值,以便更容易理解。总之,对于“ns”的固定值,我需要使用递归函数计算多项式的所有系数,并在“延迟”处计算其最终值。通过增加“延迟”中的点数,我可以看到固定“ns”的曲线。 我的问题是:对于 1 到 10 之间的任何“ns”,计算都非常快,大约为 0.069356 秒(即使对于整个“延迟”向量也是如此)。相反,对于 ns = [15] 或 [20],计算时间会增加很多(我什至没能看到结果)。 我不热衷于评估计算复杂性,所以我不知道我的代码中是否存在问题(可能是 nchoosek 函数?或 for 循环?)或者它可能是它必须考虑到这种递归的方式问题。

编辑: 正如 Adriaan 所说,我认为这确实是计算量的阶乘增长。你认为nchoosek 的任何近似值都可以用来解决这个问题吗?比如:en.wikipedia.org/wiki/Stirling%27s_approximation

The last formula in this paper 是我想要实现的(注意我更改了 tau 的 delta):

【问题讨论】:

你能给出多项式系数的公式吗?也许计算可以通过递归以外的方式完成 细化:15! = 1.3077e+12,所以如果是计算量,当然会花费很长时间,即使单次迭代的计算时间在微秒左右。相比之下:12! = 4.8e8,少了 10,000 倍,所以ns>15 需要很长时间我并不感到惊讶 不幸的是,多项式系数的公式就是那个。所以根据定义它是递归的。 你认为Striling的近似值对减少执行时间有用吗? en.wikipedia.org/wiki/Stirling%27s_approximation 作为解决方案草图:它是递归的,无需计算ns! 次。计算第一点,第二点等,完成后存储,然后乘以正确的前置因子(求和前的除法),应该就是这样。我不确定为什么您的代码在迭代中会出现阶乘增长,但不应该如此。 【参考方案1】:

我已经在你的代码上运行了配置文件,我得到了这个:

我看起来大部分时间都花在 nchoosek 上,它接受两个整数作为输入。您可以尝试预先计算所需的值并将它们存储在矩阵中以便更快地访问!


编辑:我尝试这样预先计算 nchoosek:

for i = 0 : ns
    for j = 0 : ns
        if j < i
            nchoosek_(i+1,j+1) = nchoosek(i,j);
        else
            nchoosek_(i+1,j+1) = NaN;
        end
    end
end

然后在函数内:

total = total + nchoosek_(l+1, k-1+1)*tau^(l-k+1)*get_coeff(n-1, l, tau, x , nchoosek_);

它似乎有效,并且我在 ns = 12 时得到了很好的改进:

但我还是碰壁了 ns = 15...

【讨论】:

nchoosek 呼叫在您的第二个个人资料中消失了。既然你计算了它,尽管是提前计算的,它仍然应该被考虑在内。它现在被temp&gt;get_coeff 覆盖了吗?对于ns 的较低值来说,减少 75% 的时间非常好,但是,正如我对这个问题的评论,如果我是正确的,问题是计算量的阶乘增长,在这种情况下,线性时间减少不是打算削减它(一年的计算时间减少 75% 仍然留给你 3 个月) 我省略了计算 nchoosek 的初始循环,因为它的时间可以忽略不计,并且它不会随着 ns: 0.008s 而函数内的 1.041s 缩放。我会更新答案中的数字。但是您的计算是正确的答案:显然阶乘使函数调用的数量迅速爆炸!【参考方案2】:

所以我终于设法在合理的时间内计算出系数。基本上,我接受了 Adriaan 和 rahnema1 的建议,并创建了一个 ns by ns 矩阵来存储我以递归方式计算的所有系数。因此,当递归树的某个叶子重复时,我可以通过从矩阵中提取值来修剪树。请注意,增益不是基于预先计算值(因为我在旅途中计算它们),而是基于修剪递归次数。这里有一些数字:

ns = 10 ; delay = 0:对旧递归函数的调用次数为 23713。现在,这在 175 次调用中得到解决。 对于 ns = 10; delay = [0:0.25:8]:使用旧函数调用 782529 次,执行时间为 2.74 秒,新函数调用 495 秒,速度提高约 125 倍 0.02。

【讨论】:

以上是关于Matlab递归:低效的代码还是复杂的递归?的主要内容,如果未能解决你的问题,请参考以下文章

matlab递归求解最长公共子序列(LCS)问题

低效分治算法的复杂性

非常复杂的递归代码的时间复杂度

用表存储代替递归算法

如何为给定的代码编写递归关系

递归算法的复杂性