Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/node.js/36.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript Node.js中n选择k的高效计算_Javascript_Node.js_Algorithm_Performance - Fatal编程技术网

Javascript Node.js中n选择k的高效计算

Javascript Node.js中n选择k的高效计算,javascript,node.js,algorithm,performance,Javascript,Node.js,Algorithm,Performance,我在Node.js服务器上有一些性能敏感的代码,需要计算组合。从中,我使用这个简单的递归函数来计算n选择k: function choose(n, k) { if (k === 0) return 1; return (n * choose(n-1, k-1)) / k; } 然后,因为我们都知道迭代几乎总是比递归快,所以我根据以下公式编写了此函数: 结果一致表明,迭代方法确实比Node.js中的递归方法快3到4倍(至少在我的机器上是这样) 这对于我的需求来说可能已经足够快了,

我在Node.js服务器上有一些性能敏感的代码,需要计算组合。从中,我使用这个简单的递归函数来计算n选择k:

function choose(n, k) {
    if (k === 0) return 1;
    return (n * choose(n-1, k-1)) / k;
}
然后,因为我们都知道迭代几乎总是比递归快,所以我根据以下公式编写了此函数:

结果一致表明,迭代方法确实比Node.js中的递归方法快3到4倍(至少在我的机器上是这样)

这对于我的需求来说可能已经足够快了,但是有没有办法让它更快呢?我的代码必须非常频繁地调用此函数,有时会使用相当大的值
n
k
,因此越快越好

编辑 在使用le_m和Mike的解决方案进行了几次测试之后,结果表明,虽然这两种方法都比我提出的迭代方法快得多,但Mike使用Pascal三角形的方法似乎比le_m的对数表方法略快

Recursive x 189,036 ops/sec ±8.83% (58 runs sampled)
Iterative x 538,655 ops/sec ±6.08% (51 runs sampled)
LogLUT x 14,048,513 ops/sec ±9.03% (50 runs sampled)
PascalsLUT x 26,538,429 ops/sec ±5.83% (62 runs sampled)
Fastest is PascalsLUT
在我的测试中,对数查找方法比迭代方法快26-28倍,使用Pascal三角形的方法比对数查找方法快1.3-1.8倍


请注意,我遵循了le_m的建议,即使用更高的精度预先计算对数,然后将它们转换回常规JavaScript
Number
s(始终如此)。

以下算法的运行时复杂性为O(1)给定空间复杂度为O(n)的对数因子线性查找表

nk限制在[0,1000]范围内是有意义的,因为
二项式(1000500)
已经非常接近
数字。最大值
。因此,我们需要一个1000大小的查找表

在现代JavaScript引擎上,由n个数字组成的紧凑数组的大小为n*8字节。因此,完整的查找表需要8KB的内存。如果我们将输入限制在[01100]范围内,表将只占用800字节

var logf=[0, 0, 0.6931471805599453, 1.791759469228055, 3.1780538303479458, 4.787491742782046, 6.579251212010101, 8.525161361065415, 10.60460290274525, 12.801827480081469, 15.104412573075516, 17.502307845873887, 19.987214495661885, 22.552163853123425, 25.19122118273868, 27.89927138384089, 30.671860106080672, 33.50507345013689, 36.39544520803305, 39.339884187199495, 42.335616460753485, 45.38013889847691, 48.47118135183523, 51.60667556776438, 54.78472939811232, 58.00360522298052, 61.261701761002, 64.55753862700634, 67.88974313718154, 71.25703896716801, 74.65823634883016, 78.0922235533153, 81.55795945611504, 85.05446701758152, 88.58082754219768, 92.1361756036871, 95.7196945421432, 99.33061245478743, 102.96819861451381, 106.63176026064346, 110.32063971475739, 114.0342117814617, 117.77188139974507, 121.53308151543864, 125.3172711493569, 129.12393363912722, 132.95257503561632, 136.80272263732635, 140.67392364823425, 144.5657439463449, 148.47776695177302, 152.40959258449735, 156.3608363030788, 160.3311282166309, 164.32011226319517, 168.32744544842765,  172.3527971391628, 176.39584840699735, 180.45629141754378, 184.53382886144948, 188.6281734236716, 192.7390472878449, 196.86618167289, 201.00931639928152, 205.1681994826412, 209.34258675253685, 213.53224149456327, 217.73693411395422, 221.95644181913033, 226.1905483237276, 230.43904356577696, 234.70172344281826, 238.97838956183432, 243.2688490029827, 247.57291409618688, 251.8904022097232, 256.22113555000954, 260.5649409718632, 264.9216497985528, 269.2910976510198, 273.6731242856937, 278.0675734403661, 282.4742926876304, 286.893133295427, 291.3239500942703, 295.76660135076065, 300.22094864701415, 304.6868567656687, 309.1641935801469, 313.65282994987905, 318.1526396202093, 322.66349912672615, 327.1852877037752, 331.7178871969285, 336.26118197919845, 340.815058870799, 345.37940706226686, 349.95411804077025, 354.5390855194408, 359.1342053695754, 363.73937555556347];
函数二项式(n,k){
返回Math.exp(logf[n]-logf[n-k]-logf[k]);
}

console.log(二项式(5,3));
永远不要计算阶乘,它们增长得太快。而是计算你想要的结果。在这种情况下,你想要的是二项式数字,它有一个非常简单的几何结构:你可以根据需要进行构建,然后使用简单的算术来完成

起始于[1 ]和[1,1]。下一行在[4]的中间有[1+2],[4]在结尾,[1,1,1]。下一行:[1 ]在开始时,点1中的前两个项的总和,在点三的下两个项的总和,和[1,]的结尾:[1,3,3,1]。下一行:[1,然后1 + 3=3,然后,+ +,==,然后是+ +,=,,然后[Y]。最后,等等。如你所见,没有阶乘、对数,甚至乘法:只需使用干净的整数进行超快加法。如此简单,你可以手工构建一个庞大的查找表

你应该这样做

永远不要在代码中计算您可以手工计算的内容,而只是将其作为常量包含以供立即查找;在这种情况下,为n=20左右的内容编写表是非常简单的,然后您可以将其用作“起始LUT”,甚至可能永远不会访问高位行

但是,如果您确实需要它们,或者更多,那么由于您无法构建无限查找表,您会妥协:您可以从一个预先指定的LUT开始,并使用一个函数“填充”到您需要的某个尚未包含在其中的术语:

module.exports = (function() {
  // step 1: a basic LUT with a few steps of Pascal's triangle
  var binomials = [
    [1],
    [1,1],
    [1,2,1],
    [1,3,3,1],
    [1,4,6,4,1],
    [1,5,10,10,5,1],
    [1,6,15,20,15,6,1],
    [1,7,21,35,35,21,7,1],
    [1,8,28,56,70,56,28,8,1],
    ...
  ];

  // step 2: a function that builds out the LUT if it needs to.
  function binomial(n,k) {
    while(n >= binomials.length) {
      let s = binomials.length;
      let nextRow = [];
      nextRow[0] = 1;
      for(let i=1, prev=s-1; i<s; i++) {
        nextRow[i] = binomials[prev][i-1] + binomials[prev][i];
      }
      nextRow[s] = 1;
      binomials.push(nextRow);
    }
    return binomials[n][k];
  }

  return binomial;
}());
module.exports=(函数(){
//步骤1:一个基本的LUT,包含Pascal三角形的几个步骤
变量二进制=[
[1],
[1,1],
[1,2,1],
[1,3,3,1],
[1,4,6,4,1],
[1,5,10,10,5,1],
[1,6,15,20,15,6,1],
[1,7,21,35,35,21,7,1],
[1,8,28,56,70,56,28,8,1],
...
];
//步骤2:如果需要,构建LUT的函数。
函数二项式(n,k){
而(n>=binomials.length){
设s=二进制数。长度;
设nextRow=[];
Recursive x 189,036 ops/sec ±8.83% (58 runs sampled)
Iterative x 538,655 ops/sec ±6.08% (51 runs sampled)
LogLUT x 14,048,513 ops/sec ±9.03% (50 runs sampled)
PascalsLUT x 26,538,429 ops/sec ±5.83% (62 runs sampled)
Fastest is PascalsLUT
module.exports = (function() {
  // step 1: a basic LUT with a few steps of Pascal's triangle
  var binomials = [
    [1],
    [1,1],
    [1,2,1],
    [1,3,3,1],
    [1,4,6,4,1],
    [1,5,10,10,5,1],
    [1,6,15,20,15,6,1],
    [1,7,21,35,35,21,7,1],
    [1,8,28,56,70,56,28,8,1],
    ...
  ];

  // step 2: a function that builds out the LUT if it needs to.
  function binomial(n,k) {
    while(n >= binomials.length) {
      let s = binomials.length;
      let nextRow = [];
      nextRow[0] = 1;
      for(let i=1, prev=s-1; i<s; i++) {
        nextRow[i] = binomials[prev][i-1] + binomials[prev][i];
      }
      nextRow[s] = 1;
      binomials.push(nextRow);
    }
    return binomials[n][k];
  }

  return binomial;
}());