好的,我要感谢大家的回答 - 非常有趣且乐于助人。:)
PriorityQueue 绝对是我正在寻找的正确术语 - 谢谢。现在一切都与实施有关。
这是我的想法:
让 N 是队列的大小,M 是处理时每个时间戳(可以说是“并发”事件)的平均事件数量(事件的密度不会均匀分布,“遥远的未来”蜂拥而至更稀疏,但随着时间的推移,这个时间区域变得更加密集(实际上,我认为最大密度将在未来 4 到 12 小时的某个地方)。我正在寻找一种可扩展的解决方案,它对相当大的 M 表现良好。目标是在一秒钟内真正处理这些 M 到期事件,所以我想花最少的时间来找到它们。
- 采用简单的树方法,正如多次建议的那样,我将进行 O(log N) 插入,我猜这非常好。如果我是对的,处理一个时间戳的成本将是 O(M*log N),这不再那么好了。
- 另一种选择是,拥有一个包含事件列表而不是单个事件的树。实现一些 getlistForGivenStampAndCreateIfNoneExists 操作应该是可行的,如果不存在列表,它会比下树两次快一点。但无论如何,随着 M 的增长,这甚至不应该太重要。因此插入将是 O(log N),和以前一样,处理将是 O(M+log N),我认为这也很好。
- 我制定了事件列表哈希方法。这也应该有 O(1) 的插入和 O(M) 的处理成本,尽管这对于散列来说并不是太微不足道。听起来很酷,实际上。还是我错过了什么?当然,要让一个hash表现良好并不是那么容易,但除此之外,还有什么问题吗?还是哈希有问题?Wikipedia 指出:
“在尺寸良好的哈希表中,每次查找的平均成本(指令数)与存储在表中的元素数量无关。许多哈希表设计还允许任意插入和删除键值对,以每项操作的恒定平均(实际上是摊销)成本计算。”
一个快速的基准测试表明,我的平台的标准实现似乎与此相匹配。
- DVK 提供的事件列表数组方法。这有 O(1) 插入。现在这很好。但如果我理解正确,它有 O(M+T) 处理成本,其中 T 是数组的大小(如果你愿意的话,是时隙的数量),因为从数组中移除是以线性成本为代价的。此外,这仅在存在最大时间偏移时才有效。
实际上,我想讨论数组方法。O(M+T) 不好。一点也不。但是我花了一些脑筋,这就是我想出的:
第一个想法:懒惰
O(T) 可以被任意因素压缩,引入一点惰性,但最终它会保持 O(T)。但这有多糟糕?假设 T=2419200,即 28 天。然后,我会每天清理一次(最好在预计负载较低的情况下)。这将浪费不到 5% 的阵列。在我的目标平台上,复制操作在相当旧的 2GHz 内核上需要 31 毫秒,所以这似乎不是一个坏主意。
第二个想法:块
想了想,我想到了这个解决方案:一个区间散列,一个区间(即给定的时间范围)又是一个事件列表数组。间隔的大小都是相同的,最好是简单的,比如几天或几小时。
对于插入,我通过哈希查找正确的间隔(如果不存在则创建),并在间隔中查找正确的事件列表(如果不存在则再次创建)然后插入它,即 O(1)。
对于处理,我只是通过处理当前到期的事件列表,然后处理它来获取当前间隔,并处理到期事件。数组保持不变的长度,所以我们在 O(M) (这是处理 M 个元素可以获得的最好的)。一旦当前间隔被完全处理(因此,如果间隔现在代表“过去”),我只需将其处理为 O(1)。我可以保留对当前间隔的额外引用,从而无需查找它,但我想这并没有提供任何明显的改进。
在我看来,第二次优化确实是最好的解决方案,因为它快速且不受约束。为间隔选择合适的大小可以优化内存开销与哈希查找开销。我不知道,我是否应该担心哈希查找时间。对于高M,这应该不重要,不是吗?因此,我选择间隔大小为 1,这使我回到接近数字 3。
我会非常感谢您对此的任何意见。