JavaScript函数的速度差异,找到数组中最常见的元素

时间:2015-11-03 18:02:17

标签: javascript performance

我正在面试并一直在练习一些练习题。问题是:

  

查找数组中重复次数最多的整数。

这是我创建的功能和他们创建的功能。它们被恰当地命名。

var arr = [3, 6, 6, 1, 5, 8, 9, 6, 6]

function mine(arr) {
  arr.sort()
  var count = 0;
  var integer = 0;
  var tempCount = 1;
  var tempInteger = 0;
  var prevInt = null
  for (var i = 0; i < arr.length; i++) {
    tempInteger = arr[i]
    if (i > 0) {
      prevInt = arr[i - 1]
    }
    if (prevInt == arr[i]) {
      tempCount += 1
      if (tempCount > count) {
        count = tempCount
        integer = tempInteger
      }
    } else {
      tempCount = 1
    }
  }
  console.log("most repeated is: " + integer)
}

function theirs(a) {
  var count = 1,
    tempCount;
  var popular = a[0];
  var temp = 0;
  for (var i = 0; i < (a.length - 1); i++) {
    temp = a[i];
    tempCount = 0;
    for (var j = 1; j < a.length; j++) {
      if (temp == a[j])
        tempCount++;
    }
    if (tempCount > count) {
      popular = temp;
      count = tempCount;
    }
  }
  console.log("most repeated is: " + popular)
}
console.time("mine")
mine(arr)
console.timeEnd("mine")
console.time("theirs")
theirs(arr)
console.timeEnd("theirs")

结果如下:

most repeated is: 6
mine: 16.929ms
most repeated is: 6
theirs: 0.760ms

是什么让我的功能比他们慢?

3 个答案:

答案 0 :(得分:2)

看起来这不是一个公平的考验。首先运行函数时,它会对数组进行排序。这意味着它们的功能最终使用已经排序的数据,但不会花费执行排序的时间成本。我尝试交换测试运行的顺序,并得到几乎相同的时间:

console.time("theirs")
theirs(arr)
console.timeEnd("theirs")
console.time("mine")
mine(arr)
console.timeEnd("mine")

most repeated is: 6
theirs: 0.307ms
most repeated is: 6
mine: 0.366ms

此外,如果您使用两个单独的阵列,您将看到您的功能和他们的功能大约在相同的时间内运行。

最后,请参阅Anders的回答 - 它表明较大的数据集显示您的函数的O(n * log(n))+ O(n)性能与其函数的O(n ^ 2)性能相比。

答案 1 :(得分:2)

我的测试结果

当我测试(JSFiddle)一个包含50 000个元素的随机数组时,我得到以下结果:

mine:     28.18 ms
theirs: 5374.69 ms

换句话说,您的算法似乎要快得多。这是预期的。

为什么你的算法更快?

首先对数组进行排序,然后循环一次。 Firefox使用merge sort,Chrome使用quick sort的变体(根据此question)。两者平均花费O(n*log(n))时间。然后循环遍历数组,花费O(n)时间。总共得到O(n*log(n)) + O(n),可以简化为O(n*log(n))

另一方面,他们的解决方案有一个嵌套循环,其中外部循环和内部循环都遍布所有元素。那应该是O(n^2)。换句话说,它更慢。

为什么您的测试结果不同?

那么为什么你的测试结果与我的不同?我看到了很多可能性:

  • 您使用的是小样本。如果您只是在代码中使用了九个数字,那肯定就是这种情况。在测试中使用短数组时,开销(如Gundy在评论中建议的console.log运行)占主导地位。这可以使结果看起来完全随机。
  • neuronaut建议它与它们的代码在已经按代码排序的数组上运行的事实有关。虽然这是一种糟糕的测试方式,但我看不出它会如何影响结果。
  • 某种浏览器的差异。

关于.sort()

的说明

进一步说明:您不应使用.sort()对数字进行排序,因为它会按字母顺序排序。相反,请使用.sort(function(a, b){return a-b})。阅读更多here

关于进一步说明的进一步说明:在这种特殊情况下,仅使用.sort()可能实际上更聪明。由于您不关心排序,只关心分组,因此将数字排序错误无关紧要。它仍然会将具有相同值的元素组合在一起。如果没有比较功能(我怀疑它是)更快,那么没有一个排序是有意义的。

更快的算法

您已在O(n*log(n))中解决了问题,但您可以在O(n)中完成此操作。这样做的算法非常直观。循环遍历数组,并跟踪每个数字出现的次数。然后选择出现次数最多的数字。

让我们说数组中有m个不同的数字。循环遍历数组需要O(n)并找到最大值O(m)。从O(n) + O(m)开始,这会将O(n)简化为m < n

这是代码:

function anders(arr) {

    //Instead of an array we use an object and properties.
    //It works like a dictionary in other languages.
    var counts = new Object();

    //Count how many of each number there is.
    for(var i=0; i<arr.length; i++) {
        //Make sure the property is defined.
        if(typeof counts[arr[i]] === 'undefined')
            counts[arr[i]] = 0;
        //Increase the counter.
        counts[arr[i]]++;
    }

    var max;             //The number with the largest count.
    var max_count = -1;  //The largest count.

    //Iterate through all of the properties of the counts object
    //to find the number with the largerst count.
    for (var num in counts) {
        if (counts.hasOwnProperty(num)) {
            if(counts[num] > max_count) {
                max_count = counts[num];
                max = num;
            }
        }
    }

    //Return the result.
    return max;

}

在0到49之间的50 000个元素的随机数组上运行它在我的计算机上只需3.99毫秒。换句话说,它是最快的。背面是你需要O(m)个记忆来存储每个号码出现的时间。

答案 2 :(得分:0)

这里的其他答案已经很好地解释了为什么theirs更快 - 以及如何优化你的。使用大型数据集(@Anders)实际上更好。我设法优化theirs解决方案;也许这里有用的东西。

通过采用一些基本的JS微优化,我可以获得始终如一的更快结果。这些优化也可以应用于您的原始函数,但我将它们应用于theirs

  • 预增量为slightly faster而不是后增量,因为该值不需要先读入内存
  • 反向循环massively faster(在我的机器上)比我尝试过的任何东西都要多,因为JS被翻译成操作码,and guaranteeing >= 0 is very fast.对于这个测试,我的计算机得分为514,271,438 ops / sec,而下一个最快的得分198,959,074
  • 缓存length的结果 - 对于较大的数组,这会使better明显快于theirs

代码:

function better(a) {
    var top = a[0],
        count = 0,
        i = len = a.length - 1;
    while (i--) {
        var j = len,
            temp = 0;
        while (j--) {
            if (a[j] == a[i]) ++temp;
        }
        if (temp > count) {
            count = temp;
            top = a[i];
        }
    }
    console.log("most repeated is " + top);
}

[fiddle]

theirs非常相似(如果不相同),但使用上述微优化。

以下是运行每个功能500次的结果。在运行任何函数之前对数组进行预排序,并从sort中删除mine()

mine: 44.076ms
theirs: 35.473ms
better: 32.016ms