javascript - array.prototype.includes 与 set.prototype.has 的时间复杂度

标签 javascript arrays performance set time-complexity

当涉及到 javascript 中的集合与数组时,我一直在阅读有关现代 javascript 引擎时间复杂度的相互矛盾的答案。

我完成了 codility 的演示任务,这是一个简单的作业,旨在找到以下问题的解决方案:给定一个包含 N 个整数的数组 A,返回 A 中未出现的最小正整数(大于 0)。

例如,给定 A = [1, 3, 6, 4, 1, 2],该函数应返回 5。

我的第一个解决方案是:

const solution = arr => {
    for(let int = 1;;int++) {
        if (!arr.includes(int)) {
            return int;
        }
    }
}

现在,奇怪的是,codility 说这个解决方案的时间复杂度为 O(n**2) (他们更喜欢复杂度为 O(n) 的解决方案。据我所知, array.prototype.includes 是线性搜索( https://tc39.es/ecma262/#sec-array.prototype.includes )意味着它应该具有 O(n) 时间复杂度。

如果我使用 Set 输入不同的解决方案,我会得到满分:

const solution = arr => {
  const set = new Set(arr);
  let i = 1;

  while (set.has(i)) {
    i++;
  }

  return i;
}

Codility 表示这显然具有 O(N) 或 O(N * log(N)) 的时间复杂度。

这是正确的吗? array.prototype.includes 实际上是 O(n**2) 而不是 O(n) 吗?

最后,我有点困惑为什么 Set.has() 是首选,因为在我的控制台性能测试中,Array.includes() 始终优于首先创建 Set 然后在设置,如以下代码片段所示。

const rand = (size) => [...Array(size)].map(() => Math.floor(Math.random() * size));

const small = rand(100);
const medium = rand(5000);
const large = rand(100000);

const solution1 = arr => {
  console.time('Array.includes');
  for(let int = 1;;int++) {
    if (!arr.includes(int)) {
      console.timeEnd('Array.includes');
      return int;
    }
  }
}

const solution2 = arr => {
  console.time('Set.has');
  const set = new Set(arr);
  let i = 1;
  while (set.has(i)) {
    i++;
  }
  console.timeEnd('Set.has');
  return i;
}

console.log('Testing small array:');
solution1(small);
solution2(small);
console.log('Testing medium array:');
solution1(medium);
solution2(medium);
console.log('Testing large array:');
solution1(large);
solution2(large);

如果集合查找具有更好的时间复杂度(如果这是真的)并且是 codility 的首选,那么为什么我的性能测试偏向 array.prototype.includes 解决方案?

最佳答案

我知道这是一个老问题,但我仔细检查了数据。我也假设 Set.has 会是 O(1) 或 O(log N),但在我的第一次测试中,它似乎是 O(N)。这些函数的规范也暗示了很多,但很难破译:https://tc39.es/ecma262/#sec-array.prototype.includes https://tc39.es/ecma262/#sec-set.prototype.has不过,在其他地方,他们也说 Set.has 必须是次线性的——而且我相信现代的实现是这样的。

根据经验,当我在一些代码 Playground 中运行Set.has时,它展示了线性性能......但在像节点和chrome这样的真实环境中,它们并没有令人惊讶。我不确定 Playground 在后端运行什么,但也许使用了 Set polyfill。所以要小心!

这是my test cases , trim 以消除随机性:

const makeArray = (size) => [...Array(size)].map(() => size);

const small =  makeArray(1000000);
const medium = makeArray(10000000);
const large =  makeArray(100000000);

const solution1 = arr => {
  console.time('Array.includes');
  arr.includes(arr.length - 1)
  console.timeEnd('Array.includes');
}

const solution2 = arr => {
  const set = new Set(arr)
  console.time('Set.has');
  set.has(arr.length-1)
  console.timeEnd('Set.has');
}


console.log('** Testing small array:');
solution1(small);
solution2(small);
console.log('** Testing medium array:');
solution1(medium);
solution2(medium);
console.log('** Testing large array:');
solution1(large);
solution2(large);

但在 Chrome 中:

** Testing small array:
VM183:10 Array.includes: 1.371826171875 ms
VM183:17 Set.has: 0.005859375 ms
VM183:25 ** Testing medium array:
VM183:10 Array.includes: 14.32568359375 ms
VM183:17 Set.has: 0.009765625 ms
VM183:28 ** Testing large array:
VM183:10 Array.includes: 115.695068359375 ms
VM183:17 Set.has: 0.0048828125 ms

在节点 16.5 中:

Testing small array:
Array.includes: 1.223ms
Set.has: 0.01ms
Testing medium array:
Array.includes: 11.41ms
Set.has: 0.054ms
Testing large array:
Array.includes: 127.297ms
Set.has: 0.047ms

所以,是的,数组绝对是线性的,而集合要快得多。

关于javascript - array.prototype.includes 与 set.prototype.has 的时间复杂度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65239763/

相关文章:

java - "java.lang.ArrayIndexOutOfBoundsException"当尝试创建一个检查板来创建 QRcode 的计时模式时

c++ - 如何测量计算着色器的时间性能?

javascript - bing map 添加更多图钉

php - JavaScript 无法正常运行,请快速查看以进行调试

javascript - 对象属性动态删除

javascript - 通过变量 .-string 插值访问 JSON 数据

arrays - 在 O(nlogn) 时间内使用分而治之从数组中删除重复项

c# - 在 C# 中,在性能方面复制位的最佳方法

c# - 调整阵列性能?

javascript - 每当我导入 bootstrap.bundle.min.js 时,控制台浏览器都会出现问题