这是我的基本问题:我给了currentTime
。例如,750秒。我还有一个包含1000到2000个对象的数组,每个对象都有startTime
,endTime
和_id
属性。鉴于currentTime
,我需要找到startTime
和endTime
属于该范围的对象 - 例如startTime : 740
,endTime : 755
。
在Javascript中执行此操作的最有效方法是什么?
对于初学者来说,我只是做了这样的事情:
var arrayLength = array.length;
var x = 0;
while (x < arrayLength) {
if (currentTime >= array[x].startTime && currentTime <= array[x].endTime) {
// then I've found my object
}
x++;
};
但我怀疑循环不是这里的最佳选择。有什么建议吗?
编辑:为清楚起见,currentTime
必须属于startTime
和endTime
我的解决方案:我的数据结构为我提供了一些好处,可以让我简化一些事情。我已按照建议完成了基本二进制搜索,因为数组已经按startTime排序。我还没有完全测试过这个东西的速度,但我怀疑它的速度要快一些,特别是对于更大的阵列。
var binarySearch = function(array, currentTime) {
var low = 0;
var high = array.length - 1;
var i;
while (low <= high) {
i = Math.floor((low + high) / 2);
if (array[i].startTime <= currentTime) {
if (array[i].endTime >= currentTime ){
// this is the one
return array[i]._id;
} else {
low = i + 1;
}
}
else {
high = i - 1;
}
}
return null;
}
答案 0 :(得分:5)
解决此问题的最佳方法取决于您必须调用搜索功能的次数。
如果您只调用几次,请说m
次,进行线性搜索。调用此函数的总体复杂度为O(mn)
。
如果您多次调用您的函数,并且许多我的意思超过log(n)
次,您应该:
O(nlogn)
之前按startTime
对数组进行排序,如果您有多个具有相等值endTime
startTime
排序
startTime <= x
的元素范围。这意味着执行两个二进制搜索:一个用于范围的start
,另一个用于范围的end
。这是在O(logn)
[start, end]
内进行线性搜索。您必须执行线性搜索,因为startTimes
的顺序不会告诉您endTimes
。这可以是O(1)
和O(n)
之间的任意位置,具体取决于细分的分布和x
的值。 平均情况: O(nlogn)
用于初始化,O(logn)
用于每次搜索。
最坏情况:包含许多相等段的数组,或具有公共间隔的段,并在此间隔内搜索。在这种情况下,您将O(nlogn)
进行初始化,O(n + logn) = O(n)
进行搜索。
答案 1 :(得分:2)
听起来像是binary search的问题。
答案 2 :(得分:2)
假设您的搜索数组是长寿命且相对恒定的,第一次迭代将按开始时间对所有数组元素进行排序(或者如果您不想要,则创建指向数组元素的已排序开始时间的索引他们排序)。
然后你可以有效地(使用二进制印章)折扣,开始太晚了。对其他人的顺序搜索会更快。
即使更多速度,也要为开始和结束时间维护单独的排序索引。然后按照前面提到的相同操作扔掉那些开始得太晚的操作。
然后,对剩下的那些,使用结束时间索引扔掉那些过早结束的那些,你剩下的就是你的候选名单。
但是,确保这实际上是需要的。两千个元素看起来不是很大,所以你应该对当前方法进行计时,并且只有在确实存在问题时才尝试优化。
答案 3 :(得分:1)
根据给出的信息,无法确定什么是最佳解决方案。如果数组未排序,则循环是单个查询的最佳方式。沿阵列的单次扫描只需要O(N)(其中N是数组的长度),而对它进行排序然后进行二进制搜索将需要O(N log(N)+ log(N)),因此它在这种情况下会花更多的时间。
如果在同一个大型阵列上有大量不同的查询,分析会有很大的不同。如果在同一个阵列上有大约N个查询,则排序实际上可能会提高性能,因为每个Query将采用O(log(N))。因此,对于N个查询,它将需要O(N log(N))(现在剩余的log(N)被丢弃)而未排序的搜索也将采用明显更大的O(N ^ 2)。当排序开始产生影响时,也取决于数组的大小。
当您经常更新阵列时,情况也会有所不同。更新未排序的数组可以在O(1)分期中完成,而更新排序的数组则需要O(N)。因此,如果你有相当频繁的更新,排序可能会受到伤害。
范围查询还有一些非常有效的数据结构,但是如果它们有意义,它又取决于实际使用情况。
答案 4 :(得分:1)
如果数组没有排序,那么你的数组是正确的。
不要陷入思考首先对数组进行排序,然后应用搜索的陷阱。
使用您尝试过的代码,您的复杂性为 O(n),其中 n 是元素的数量。
如果您首先对数组进行排序,首先会在average case
中陷入 O(n log(n))(与Sorting algorithm比较)的复杂性。
然后你必须应用二进制搜索,它的执行平均复杂度为 O(log_ 2(n) - 1)。
所以,你最终会花费,在一般情况下:
O(n log(n) + (log_2(n) - 1))
而不只是 O(n)。
答案 5 :(得分:1)
interval tree是一种数据结构,如果总共有n个区间,则允许在O(lg n)时间内(平均和最差情况下)回答此类查询。构造数据结构的预处理时间是O(n lg n);空间是O(n)。 augmented区间树的插入和删除时间为O(lg n)。如果m个间隔覆盖一个点,则回答所有间隔查询的时间是O(m + lg n)。 Wikipedia描述了几种间隔树;例如,居中间隔树是第三树,每个节点存储:
•中心点
•指向另一个节点的指针,该节点包含完全位于中心点左侧的所有间隔 •指向另一个节点的指针,该节点包含完全位于中心点右侧的所有间隔 •所有与中心点重叠的间隔按其起点分类 •所有间隔重叠中心点按其结束点排序
注意,对于找到一个覆盖点的间隔的平均和最坏情况查询,间隔树具有O(lg n)复杂度。以前的答案具有相同的O(n)最坏情况查询性能。之前的几个答案声称它们的平均时间为O(lg n)。但他们都没有提供证据;相反,他们只断言平均表现为O(lg n)。之前答案的主要特征是使用二进制搜索开始时间。然后有人说使用线性搜索,而其他人说使用二进制搜索,结束时间,但没有明确后一个搜索结束的间隔集。他们声称平均表现为O(lg n),但这仅仅是一厢情愿的想法。正如维基百科文章 Naive Approach 标题下所指出的那样,
一种天真的方法可能是构建两个并行树,一个按起始点排序,另一个按每个间隔的结束点排序。这允许在O(log n)时间内丢弃每棵树的一半,但结果必须合并,需要O(n)时间。这给了我们O(n + log n)= O(n)的查询,这不比蛮力好。