如何更有效地计算二项式的和?

时间:2016-08-24 08:58:58

标签: matlab performance time vectorization mex

我必须按如下方式计算方程式:

enter image description here 给出k1,k2的地方。我正在使用MATLAB来计算P。我想我对上面的等式有正确的实现。但是,我的实施速度很慢。我认为这个问题来自二项式系数。从这个等式,我可以有一个有效的方法来加快时间吗?谢谢大家。

对于k1=150; k2=150; D=200;,需要 11.6秒

function main
warning ('off');
  function test_binom()
      k1=150; k2=150; D=200; P=0;
      for i=0:D-1
          for j=0:i
              if (i-j>k2||j>k1) 
                  continue;
              end
              P=P+nchoosek(k1,j)*nchoosek(k2,i-j)/nchoosek((k1+k2),i);          
          end 
      end
  end
f = @()test_binom(); 
timeit(f)
end

更新:对于测量时间,我发现nchoosek是计算时间很长的原因。因此,我重写了如下函数

function re=choose(n, k)
    if (k == 0)
        re=1;
    else
        re=(n * choose(n - 1, k - 1)) / k;
    end
end

现在,计算时间缩短为0.25秒。有没有更好的方法?

3 个答案:

答案 0 :(得分:3)

您可以将nchoosek的结果保存到表中以防止重复评估该函数,同时提供二项式系数的实现:

cython

你的nchoosek2代码与此相比: online demo

答案 1 :(得分:2)

您可以对所有过程进行矢量化,并且无需使用mex即可实现超快速。

首先是SERIAL_NUMBER函数:

nchoosek

然后function C = nCk(n,k) % use smaller k if available k(k>n/2) = n-k(k>n/2); k = k(:); kmat = ones(numel(k),1)*(1:max(n-k)); kmat = kmat.*bsxfun(@le,kmat,(n-k)); pw = bsxfun(@power,kmat,-1./(n-k)); pw(kmat==0) = 1; kleft = ones(numel(k),1)*(min(k):n); kleft = kleft.*bsxfun(@gt,kleft,k); t = bsxfun(@times,kleft,prod(pw,2)); t (kleft==0) = 1; C = prod(t,2); end beta计算:

P

执行时间从 10.674 下降到 0.49696 秒。

修改

考虑到@ rahnema1,我设法使用一个表进行所有独特的function P = binomial_coefficient(k1,k2,D) warning ('off','MATLAB:nchoosek:LargeCoefficient'); i_ind = nonzeros(triu(ones(D,1)*(1:D)))-1; j_ind = nonzeros(tril(ones(D,1)*(1:D+1)).')-1; valid = ~(i_ind-j_ind>=k2 | j_ind>=k1); i_ind = i_ind(valid); j_ind = j_ind(valid); beta = @(ii,jj) nCk(k1,jj).*nCk(k2,ii-jj)./nCk((k1+k2),ii); b = beta(i_ind,j_ind); P = sum(b(:)); end 计算,这样做更快,因此不会多次执行任何操作。使用上面相同的nCk函数,这就是新nCk函数的外观:

binomial_coefficient

现在,当只需 0.01212 秒运行时,它不仅仅是超快代码,它还是flying-talking-super-fast代码!

答案 2 :(得分:1)

rahnema1's回答有一个非常好的方法:创建一个您生成一次并稍后访问的值表(以及其他一些聪明的优化)。

我要改变的一件事是计算二项式系数的方式。如果您考虑计算nchoosek(n, k)nchoosek(n, k+1)的阶乘,则两次都会重新计算n!,而对于k+1,您需要重新计算k!和乘以k+1。 (同样适用于(n-k)!。)

我们可以根据nchoosek(n, k+1)的值迭代计算nchoosek(n, k),而不是每次都丢弃计算。

function L=combList(n, maxk)
% Create a vector of length maxk containing
%   [nchoosek(n, 1), nchoosek(n, 2), ..., nchoosek(n, maxk)]
% Note: nchoosek(n, 0) == nchoosek(n, n) == 1

assert(maxk<=n, 'maxk must be less than or equal to n');

L = zeros(1,maxk);
L(1) = n;                    % nchoosek(n, 1) == n
for k = 2:maxk
   L(k) = L(k-1)*(n-k+1)/k;
end

在您的计划中,您只需为k1k2k1+k2创建3个具有相应限制的列表,然后将这些列表编入索引以生成总和。