Set.has() 方法 O(1) 和 Array.indexOf O(n) 吗? [复制]
Posted
技术标签:
【中文标题】Set.has() 方法 O(1) 和 Array.indexOf O(n) 吗? [复制]【英文标题】:Is the Set.has() method O(1) and Array.indexOf O(n)? [duplicate] 【发布时间】:2019-07-30 03:08:48 【问题描述】:我在一个答案中看到Set.has()
方法是 O(1),Array.indexOf()
是 O(n)。
var a = [1, 2, 3, 4, 5];
a.indexOf(5);
s = new Set(a);
s.has(5); //Is this O(1)?
Set.has()
真的是 O(1) 吗?
【问题讨论】:
规范要求方法在 sublinear 时间内运行。虽然O(1)
的复杂性不是保证,但如果环境完全支持 Set,IIRC 很可能是您在普通浏览器环境中遇到的情况。
【参考方案1】:
如果阅读has()
的specification,有一个算法描述它:
Set.prototype.has(value)
的算法:
采取以下步骤:
设 S 为 this 值。 如果 Type(S) 不是 Object,则抛出 TypeError 异常。 如果 S 没有 [[SetData]] 内部槽,则抛出 TypeError 异常。 让条目是列表,它是 S 的 [[SetData]] 内部槽的值。 对作为条目元素的每个 e 重复, 如果 e 不为空且 SameValueZero(e, value) 为 true,则返回 true。 返回 false。
显然,基于该算法和 REPEAT
这个词的存在,人们可能会混淆它是 O(1)
(我们可以认为它可能是 O(n)
)。但是,在specification 上,我们可以看到:
必须使用哈希表或其他机制来实现集合对象,这些机制提供的访问时间平均而言与集合中的元素数量呈次线性关系。
感谢 @CertainPerformance 指出这一点。
所以,我们可以创建一个测试来比较 Array.indexOf()
和 Set.has()
在最坏的情况下,即寻找一个根本不在数组中的项目(感谢 @aquinas用于指出此测试):
// Initialize array.
let a = [];
for (let i = 1; i < 500; i++)
a.push(i);
// Initialize set.
let s = new Set(a);
// Initialize object.
let o = ;
a.forEach(x => o[x] = true);
// Test Array.indexOf().
console.time("Test_Array.indexOf()");
for (let i = 0; i <= 10000000; i++)
a.indexOf(1000);
console.timeEnd("Test_Array.indexOf()");
// Test Set.has().
console.time("Test_Set.has()");
for (let i = 0; i <= 10000000; i++)
s.has(1000);
console.timeEnd("Test_Set.has()");
// Test Object.hasOwnProperty().
console.time("Test_Object.hasOwnProperty()");
for (let i = 0; i <= 10000000; i++)
o.hasOwnProperty(1000);
console.timeEnd("Test_Object.hasOwnProperty()");
.as-console background-color:black !important; color:lime;
.as-console-wrapper max-height:100% !important; top:0;
现在我们可以看到Set.has()
的性能优于Array.indexOf()
。还有一个与Object.hasOwnProperty()
的额外比较可作为参考。
结论:
虽然不能保证O(1)
的复杂性,但规范要求该方法在次线性时间 内运行。而Set.has()
通常会比Array.indexOf()
表现更好。
另一个测试:
在下一个示例中,我们将生成一组随机的样本数据,然后使用它来比较不同的方法。
// Generate a sample array of random items.
const getRandom = (min, max) =>
return Math.floor(Math.random() * (max - min) + min);
let sample = Array.from(length: 10000000, () => getRandom(0, 1000));
// Initialize array, set and object.
let a = [];
for (let i = 1; i <= 500; i++)
a.push(i);
let s = new Set(a);
let o = ;
a.forEach(x => o[x] = true);
// Test Array.indexOf().
console.time("Test_Array.indexOf()");
for (let i = 0; i < sample.length; i++)
a.indexOf(sample[i]);
console.timeEnd("Test_Array.indexOf()");
// Test Set.has().
console.time("Test_Set.has()");
for (let i = 0; i < sample.length; i++)
s.has(sample[i]);
console.timeEnd("Test_Set.has()");
// Test Object.hasOwnProperty().
console.time("Test_Object.hasOwnProperty()");
for (let i = 0; i < sample.length; i++)
o.hasOwnProperty(sample[i]);
console.timeEnd("Test_Object.hasOwnProperty()");
.as-console background-color:black !important; color:lime;
.as-console-wrapper max-height:100% !important; top:0;
最后,我想道歉,因为我的答案的第一个版本可能引起的混乱。感谢大家让我更好地理解我的错误。
【讨论】:
我也是这么想的。 嗯,是的,当数组中有 5 个元素时,indexOf
的性能优于集合。更改您的测试以创建一个包含 1,000,000 个项目的数组。然后,使用 worst 案例测试:查找根本不在数组中的项目。在我的机器上,如果我这样做,并且只有 10,000 次循环迭代,indexOf 需要 12,374 毫秒,而设置需要 0.5 毫秒。
规范还规定:必须使用哈希表或其他机制来实现集合对象,这些机制平均而言提供的访问时间与集合中的元素数量呈次线性关系。本 Set 对象规范中使用的数据结构仅用于描述 Set 对象所需的可观察语义。
当我运行这个测试时,a.indexOf()
的执行速度比 s.has()
(Chromium 81) 快 2 倍
indexOf
在我运行这些 (Chrome 85) 时优于 has
一个数量级【参考方案2】:
我不认为有 5 个元素的数组是检查时间复杂度的好例子。
所以基于@Shidersz 的sn-p,我制作了一个新的,它有许多元素并被调用一次。
Set.has() 真的是 O(1) 吗?
是的。根据下面的测试结果,Set.has() 的时间复杂度为 O(1)。
const MAX = 10000000
let a = []
a.length = MAX
for (let i = 0; i < MAX; i++)
a[i] = i
let s = new Set(a)
let o = a.reduce((acc, e) =>
acc[e] = e
return acc
, )
console.time("Test_Array.IndexOf(0)\t")
a.indexOf(0);
console.timeEnd("Test_Array.IndexOf(0)\t")
console.time("Test_Array.IndexOf(n/2)\t")
a.indexOf(MAX / 2);
console.timeEnd("Test_Array.IndexOf(n/2)\t")
console.time("Test_Array.IndexOf(n)\t")
a.indexOf(MAX);
console.timeEnd("Test_Array.IndexOf(n)\t")
console.time("Test_Set.Has(0)\t\t")
s.has(0)
console.timeEnd("Test_Set.Has(0)\t\t")
console.time("Test_Set.Has(n/2)\t")
s.has(MAX / 2)
console.timeEnd("Test_Set.Has(n/2)\t")
console.time("Test_Set.Has(n)\t\t")
s.has(MAX)
console.timeEnd("Test_Set.Has(n)\t\t")
console.time("Test_Object[0]\t\t")
o[0]
console.timeEnd("Test_Object[0]\t\t")
console.time("Test_Object[n/2]\t")
o[MAX / 2]
console.timeEnd("Test_Object[n/2]\t")
console.time("Test_Object[n]\t\t")
o[MAX]
console.timeEnd("Test_Object[n]\t\t")
.as-console
background-color: black !important;
color: lime;
.as-console-wrapper
max-height: 100% !important;
top: 0;
【讨论】:
有帮助,我相信你是对的,但最好进行长时间运行的测试 - IIRC,小于 10 毫秒的时间单位并不是非常值得信赖的。跨度> 我同意。但由于两种方法运行时间之间的差异很大,我认为这足以显示它们的时间复杂度,我不想通过长时间运行 sn-p 来惹恼 SO 用户。以上是关于Set.has() 方法 O(1) 和 Array.indexOf O(n) 吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章