我有一个函数,将X作为参数,并从2D数组中随机选择一个元素。
2D数组具有数千个元素,每个元素对X的要求不同,存储在arr[Y][1]
中。
例如
arr[0]
仅应在X大于4时选择。(arr[0][1] = 4+
)然后仅在X介于37和59之间时选择arr[33]
。(arr[33][1] = 37!59
)
仅在X小于79时才选择arr[490]
。(arr[490][1] = 79-
)
还有更多,大多数具有不同的X要求。
解决此问题所需的最少空间和最少元素重复的最佳方法是什么?
最糟糕的方法是将每个X的可能选择存储在2D数组中。但这会导致大量重复,从而浪费太多内存。
然后,我考虑过使用三个数组,将X +要求,X-和X范围分开。但这对我来说仍然太基础了,有更好的方法吗?
答案 0 :(得分:0)
这里的一个选项就是所谓的“接受/拒绝采样”:您选择一个随机索引 i 并检查该索引是否满足X条件。如果是这样,则返回 arr [i] 。如果不是,则随机选择另一个索引,然后重复进行直到找到某物为止。
只要大多数条件满足 i 的大多数条件,性能就会很好。如果不是这种情况-如果许多 X 的值仅满足极少数条件-那么尝试预先计算可让您进行一些计算可能很有意义查找(或缩小)给定 X 允许的索引。
如何执行此操作取决于您在每个索引上所允许的条件。例如,如果每个条件都像您在示例中一样由一个间隔给出,则可以对列表进行两次排序,首先按左端点,然后按右端点。然后,确定特定X值的有效索引将导致其左端点小于或等于X的区间与右端点大于或等于X的区间相交。
当然,如果您允许除“ X在此间隔内”之外的条件,那么您将需要其他算法。
答案 1 :(得分:0)
虽然我相信重采样将是您的最佳解决方案(数十次重采样是非常便宜的价格),但这是我在实践中永远不会实现的算法(因为它使用了非常复杂的数据结构并且操作较少比重采样有效),但有可证明的界限。每个查询都需要O(n log n)
预处理时间,O(n log n)
内存和O(log n)
时间,其中n
是您可能采样的元素数量。
您将所有范围的所有末端存储在一个数组中(称为ends
)。例如。在您的情况下,您有一个数组[-infty, 4, 37, 59, 79, +infty]
(它可能需要一些调整,例如在范围的右端添加+1;现在不重要了)。这个想法是,对于任何X
,我们只需要确定它位于哪一端。例如。如果X=62
在[59; 79]
范围内(我将此类对称为时间间隔)。然后,对于每个间隔,您将存储一组所有可能的范围。对于输入X
,您只需找到间隔(使用二进制搜索),然后输出一个与该间隔相对应的随机范围。
如何计算每个间隔的相应范围集?我们在ends
数组中从左到右。假设我们计算当前间隔的集合,然后转到下一个。这些间隔之间有一些终点。如果它是某个间隔的左端,则将相应的范围添加到新集合中(因为我们输入了该范围)。如果是正确的选择,我们将删除范围。我们如何在O(log n)
而不是O(n)
的时间内做到这一点?不可变的平衡树集可以做到这一点(本质上,它们创建新树而不是修改旧树)。
如何从集合中返回一致的随机范围?您应该增加树集:每个节点应知道其子树包含多少个节点。首先,您对范围为[0; size(tree))
的整数进行采样。然后,您查看您的根节点及其子节点。例如,假设您对整数15进行了采样,并且左子级子树的大小为10,而右子级子树的大小为20。然后转到右子级(自15 >= 10
起)并以整数5处理(自{ {1}})。您最终将访问对应于单个范围的一片叶子。返回此范围。
很抱歉,如果很难理解。就像我说的那样,在最坏的情况下上限不是一个简单的方法(其他方法在最坏的情况下需要线性时间;如果没有元素满足限制,重采样可能会无限期地运行)。它还需要进行仔细的处理(例如,当某些范围的端点一致时)。