【问题标题】:Data structure for updating values and querying the state of values at a time in the past用于更新值和查询过去某个时间值状态的数据结构
【发布时间】:2023-03-19 08:00:02
【问题描述】:

假设您对一堆独立的时变值感兴趣,每个值都代表某物的当前状态。这些值不会按任何固定时间表更改,并且无法从旧值预测新值。举一个具体的例子,假设您有一堆股票,并且您有兴趣跟踪它们的价值,并且每当对该股票进行交易时,您都会获得有关单个股票的更新。 (我的实际问题与股票无关,但希望它们能让我的理解更容易理解。)

除了只知道每只股票的当前价格之外,您还希望能够选择过去的任意时间点并获得一个“快照”,告诉您每只股票的最新交易价格是多少当时。因此,例如,您应该能够说“截至上周二下午 4:53,我跟踪的每只股票的最新价值是多少?”并有效地得到准确的答案。

我可以想到三种方法来做到这一点,但我对其中任何一种都不太满意。

1.保留日记。 按时间顺序保留所有交易的清单。更新只是添加到列表中,查询是从时间戳等于或早于指定时间戳的第一个条目开始的线性向后扫描。这将使更新成为一个恒定时间操作,但您可能必须扫描整个日志以找到所有交易的值,因此更新为 O(1),快照为 O(u),其中 u 是更新的总数。由于显而易见的原因,所需的内存是 O(u)。

2。编写检查点。 像以前一样维护一个日志,但不是每个条目都只包含新股票价格,而是更新包含 每个 股票的当前价格(截至该更新)。这计算起来很便宜:由于上次更新也包含所有这些信息,您只需将其全部复制,除了价格实际发生变化的一只股票。现在可以使用 O(log u) 操作来完成快照(在日志上使用二进制搜索来定位在指定时间戳之前或之上的最后一个条目)。然而,更新变为 O(s),其中 s 是系统中的股票数量,而且所需的总内存从第一个策略中的 O(u) 变为 O(s*u) ——如果这两个都是问题s 和 u 都很大。

3。分开的期刊。 为每只股票维护一个单独的日志,并将每只股票的更新写入其自己的日志,再次按时间顺序。要快照,请检查每个期刊并使用二进制搜索来找到正确的更新。它需要 O(u) 内存,更新是 O(1) 操作,快照可以在 O(s * log u) 时间内完成。这是三种方法中我最喜欢的方法,但我觉得它可能会有所改进,因为它忽略了不同股票更新时间之间的任何关系。

我错过了更好的方法吗?这是一个已经研究过并有普遍接受的解决方案的问题吗?

【问题讨论】:

  • 查看 VCS(如 Mercurial)如何处理文件:它们通常被认为可以在空间和速度要求之间提供良好的折衷。

标签: algorithm


【解决方案1】:

查看关于持久数据结构的文献。特别是,this early paper 描述了持久二叉搜索树的构造,该树维护对数运算,但可以在任何版本(例如时间点)访问。对某些特定版本中未更新的结构部分的访问自然会查找上一个先前版本。因此,您将在 O(log s) 时间内进行自然操作,如果您预先知道所有密钥并且永远不必重新平衡,则该结构可能占用 O(u) 空间,或者如果每次更新都会修改 O(log s) 指针。

These class notes 似乎用相当简单的术语描述了您需要实现的内容。

【讨论】:

    【解决方案2】:

    我怀疑你会找到一个在所有方面都非常出色的解决方案。你选择什么很大程度上取决于你愿意做出什么样的权衡。如果快照不常见,#3 很好;如果它们很频繁,可能不会:例如,O(S log U) 可能是源代码控制存储库的杀手。

    以下是我脑海中的一些其他想法:

    4.定期检查点。 在指定的时间间隔(每 x 小时,每 y 次更新,等等)创建一个包含每只股票当前价格的检查点。找出过去某个时间点的数据意味着查找该时间之前的最新快照,然后添加单个更新。这将具有与 #2 相同的渐近性能,但更新和内存使用的乘法常数会低很多,因为您将拍摄更少的快照。

    5.仅 Delta 检查点。 与 #4 相同,但不拍摄整个系统的快照。而是仅存储自上一个检查点以来已更改的项目。未更改的条目将在以前的检查点中查找。这在编写检查点时节省了大量时间,并大大减少了内存使用量。如果 ΔU 是检查点之间的平均更新次数,那么这两个现在都是 O(ΔU)。这实际上是一个固定数额;数据库会随着时间的推移而增长,但不是每个检查点的平均更新次数。那么,您可以将更新时间视为摊销 O(1),将内存使用视为 O(U)。

    对于它的价值,几年前我写了一个维基克隆。我遇到的问题之一是如何存储页面增量。我是只存储差异,还是每次更新都存储整页文本?如何平衡速度与内存使用量?连续应用数十或数百个差异来重建页面可能会过于缓慢,但如果有人只更改一个句子,则存储整个页面将非常浪费。

    我想要一些即使对于经常更新的大型页面也能很好扩展的东西。

    我最终采用了类似于 #5 的混合方法。我使用定期全页快照存储差异。为了确定何时拍摄快照,我将新页面文本与最新快照的文本进行比较。如果差异大小超过整页文本的一半,我将存储整页文本而不是差异。这样,如果人们进行小的更新,那么我可以存储差异,但最终一旦页面发生了足够的变化,我就会拍摄新的快照。

    【讨论】:

    • 如果我没记错的话 #4 是 Mercurial 用来存储每个文件的历史记录的。
    【解决方案3】:

    Novelocrat 提出的持久数据结构的想法似乎是一般情况下的最佳解决方案。我想它会在你的情况下正常工作。

    我只是想到了 (2) 的变体。管理按修改时间戳排序的动态数组。每个条目对应一个版本,它由 s 项组成的数组。与其存储每个版本的所有库存记录,不如懒惰地进行;创建版本时,只会为一个值已更改的库存项目分配新记录。其他 s-1 项指向 null。

    在时间 T 和股票 S 上执行搜索时,您应该从时间 T 之前的最新版本开始向后线性扫描版本。继续扫描,直到找到 S 的非空值。然后通过修复您以自己的方式找到的所有 S 的空指针,以便对它们的下一个查询是有效的。

    此解决方案提供 O(1) 加法时间,以及 O(log u) 的 摊销 查询时间。完整的快照查询耗时 O(s+log u),优于实现 (4)。空间仍然是 O(u*s)。

    查询的摊销成本源于这样一个事实:每当我们查询版本 V 的项目 S 时,版本

    【讨论】:

      猜你喜欢
      • 2023-02-05
      • 1970-01-01
      • 2018-08-10
      • 2014-06-01
      • 2015-07-12
      • 2023-02-22
      • 2021-11-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多