【问题标题】:Implementing a smart-list实现智能列表
【发布时间】:2014-07-21 03:11:10
【问题描述】:

我被要求设计一个名为 smart-list 的数据结构,它保存具有真实键号的项目并提供下一个操作:

Insert(x) - 向列表中插入一个新元素。应该是 O(log n)。

移除最小值/最大值 - 移除并返回列表中的最小值/最大值元素。应该在 O(log n) 时间内。

Transform - 改变remove min/max的返回对象(如果是min则为max,反之亦然)。应该在 O(1) 中。

随机样本(k) - 返回从列表中随机选择的 k 个元素(k 大于 0 且小于 n)。应该在 O(min(k log k, n + (n-k) log (n-k)))。

关于结构的假设: 数据结构在任何阶段都不会包含超过 3n 个元素。 我们不能假设 n=O(1)。 我们可以使用 Random() 方法在 O(1) 时间内返回 [0,1) 和预成型之间的实数。

我设法使用最小-最大精细堆实现了前三种方法。但是,我对这个时间限制内的随机样本(k)方法一无所知。我能找到的只有“Reservoir sampling”,它在 O(n) 时间内运行。

有什么建议吗?

【问题讨论】:

  • 你能解释一下你所说的“转换”操作是什么意思吗?我猜一棵红黑树似乎适合你的情况。
  • 这是一道作业题吗?我怀疑在现实生活中是否有人会要求您在“O(min(k log k, n + (n-k) log (n-k)))”中找到样本。如果是家庭作业,那就去找你的助教。

标签: algorithm data-structures random-sample


【解决方案1】:

您可以通过在数组中实现的最小-最大堆来完成所有这些工作,包括随机抽样。

对于随机抽样,从 0 到 n 中选择一个随机数。那是您要删除的项目的索引。复制该项目,然后将该索引处的项目替换为数组中的最后一项,并减少计数。现在,根据需要将该项目冒泡或筛选。

如果它处于最低级别并且该项目小于其父项,则将其冒泡。如果它比它最小的孩子大,把它筛下来。如果它处于最高级别,则反转逻辑。

随机抽样是 O(k log n)。也就是说,您将从 n 项的堆中删除 k 项。这与对 delete-min 的 k 次调用具有相同的复杂性。

其他信息

如果您不必从列表中删除项目,那么您可以通过从数组中选择 k 索引在 O(k) 中进行简单的随机抽样。但是,有重复的机会。为避免重复,您可以这样做:

当您随机选择一个项目时,将其与数组中的最后一个项目交换并将计数减 1。当您选择了所有项目时,它们位于数组的最后一个 k 位置。这显然是一个 O(k) 操作。您可以复制要由该函数返回的那些项目。然后,将 count 设置回原始值并调用 MakeHeap 函数,该函数可以从 O(n) 中的任意数组构建堆。所以你的操作是 O(k + n)。

MakeHeap 函数非常简单:

for (int i = count/2; i >= 0; --i)
{
    SiftDown(i);
}

另一种选择是,当您进行交换时,将交换操作保存在堆栈上。也就是说,保存 from 和 to 索引。要放回项目,只需以相反的顺序运行交换(即从堆栈中弹出,交换项目,然后继续直到堆栈为空)。选择的时间为 O(k),放回的时间为 O(k),堆栈的空间为 O(k)。

当然,另一种方法是按照我的建议进行删除,一旦完成所有删除,您就可以将项目重新插入堆中。这是 O(k log n) 删除和 O(k log n) 添加。

可以顺便说一句,通过使用哈希表保存随机选择的索引,在 O(k) 最佳情况下进行随机抽样。您只需生成随机索引并将它们添加到哈希表(不接受重复项),直到哈希表包含 k 项。这种方法的问题在于,至少在理论上,算法可能无法终止。

【讨论】:

  • 也许我的打字把你弄糊涂了。随机抽样不应该从堆中删除项目,只随机返回它们。它会改变你描述的时间限制吗?
  • @user3680924:如果您不删除项目,您有多种选择。查看我的更新。
【解决方案2】:

如果您将数字存储在一个数组中,并使用自平衡二叉树来维护它们的排序索引,那么您可以在给定的时间复杂度下执行所有操作。在树的节点中,您需要指向数字数组的指针,在数组中,您需要返回指向该数字所属的树节点的指针。

  • Insert(x) 将 x 添加到数组的末尾,然后将其插入到二叉树中。
  • 移除最小值/最大值沿着二叉树的左/右分支找到最小值或最大值,然后将其移除。您需要将数组中的最后一个数字交换到移除产生的孔中。这时您需要将数组中的反向指针返回到树中。
  • Transform 为删除最小/最大操作切换位
  • 随机样本在 0...n-1 范围内选择 k 或 (n-k) 个唯一整数(取决于 2k

假设可以在 O(1) 时间内分配(未初始化的)内存,则可以在 O(k) 时间内创建一组 0..n 范围内的唯一 int。

首先,假设您有办法知道内存是否未初始化。然后,您可以有一个大小为 n 的未初始化数组,并执行 Fisher-Yates 洗牌的通常 k 步,除非您每次访问数组的元素(例如索引 i),如果它未初始化,那么您可以将其初始化为值 i。这避免了初始化整个数组,从而允许在 O(k) 时间内完成洗牌,而不是 O(n) 时间。

其次,显然一般不可能知道内存是否未初始化,但是您可以使用一个技巧(以使用的内存量加倍为代价),让您在未初始化的内存中实现稀疏数组。 Russ Cox 的博客对此进行了深入描述:http://research.swtch.com/sparse

这为您提供了一种随机选择 k 个数字的 O(k) 方式。如果 k 很大(即:> n/2),您可以选择 (nk) 个数字而不是 k 个数字,但您仍然需要将未选择的数字返回给用户,这总是 O( k) 如果你把它们复制出来,那么更快的选择不会给你带来任何好处。

如果您不介意放弃对内部数据结构的访问权限,一种更简单的方法是对底层数组执行 k 或 nk 步 Fisher-Yates 洗牌(取决于 k 是否

【讨论】:

  • 您可能需要详细说明随机样本的工作原理,尤其是考虑到复杂性。
  • 这个想法非常好,谢谢。只有一个问题我不确定——自平衡树。你是说AVL树吗?而且当我移除最小/最大项目时,平衡不会影响时间限制吗?
  • @Dukeling 我添加了两种不同随机抽样方法的详细信息。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-04-14
  • 1970-01-01
  • 2011-07-03
  • 2010-11-11
  • 1970-01-01
  • 1970-01-01
  • 2011-07-25
相关资源
最近更新 更多