如何在指数级大列表中找到第 k 个最大的元素?
Posted
技术标签:
【中文标题】如何在指数级大列表中找到第 k 个最大的元素?【英文标题】:How can I find the k-th largest element in an exponentially large list? 【发布时间】:2021-11-01 04:17:26 【问题描述】:假设有 n 组实数:S[1], S[2], ..., S[n]
。关于这些集合,我们知道两件事:
每个集合 S[i] 正好有 3 个元素。
每个集合 S[i] 中的所有元素都是 [0, 1] 范围内的实数。 (不过,我不知道这个细节是否有助于解决问题)。
让我们考虑可以表示为p[1] * p[2] * p[3] * ... * p[n]
的所有数字的集合T
,其中p[i] 是S[i] 的一个元素。这个集合T
,显然有 3^n 个元素。
我的问题是,给定集合 S[1], S[2], ..., S[n]
(1 T 中比在O(3^n) 时间?重要的是,我不仅需要第 k 个最大的数字,还需要产生它的相应数字 (p[1], p[2], p[3], ... , p[n]
)。
即使答案是否定的,我也希望您能提供任何关于您将如何近似地解决这个问题的提示,也许,通过使用一些启发式方法?我知道beam search,但也许您可以提出其他建议?甚至对于束搜索,也不清楚如何在这里以最好的方式实现它。
如果可以在少于 O(3^n) 的时间内通过算法获得确切的答案,如果您能指出解决方案,我将不胜感激。
【问题讨论】:
“显然,这个集合 T 有 3^n 个元素。” 它有大约 3^n 个元素。但它的元素可能较少,因为某些乘积是相等的,即使您对 S[i] 集合中的数字有一些唯一性假设。 如果 T 实际上不太可能具有严格小于 3^n 的元素,我会感到非常惊讶。除非您有一些非常强的假设,例如“集合 S[i] 中的数字是成对互质数”。但这只是我的吹毛求疵——T 的大小仍然是 n 的指数。 所以一个观察结果是你的元组有一个自然的偏序。假设每个S
都已排序,那么我们可以说(p_1, p_2, ..., p_n) <= (q_1, q_2, ..., q_n)
为所有i
提供p_i <= q_i
。 prod
尊重此排序,因此如果在poset 排序中有> 10 个更大的元组,那么我们知道该元组有> 10 个更大的产品。然后需要一些组合学来计算有多少类型的元组在poset ordering中具有10个或更少的更大元素。
嗯,例如,“找到 T 的最大元素”很容易:只需选择每个 S[i] 的最大元素,它们的乘积就是 T 的最大元素(假设所有数字是非负数)。然后找到第二大的就是找到将哪些因子更改为稍小的因子的问题。等等。然后再次为第三大。所以看起来你可以在 k 步中找到第 k 个最大的,并且每一步大约需要 n 次操作
"每个集合S[i]中的所有元素都是[0, 1]范围内的实数。(不知道这个细节对解决有没有帮助,不过)。” 是的,这很有帮助。数字小于 1 的事实可能并不重要,但它们是非负的这一事实非常重要,并且避免了诸如“如果负因子的数量是奇数则结果为负;如果负面因素的数量即使结果是正面的”
【参考方案1】:
嗯,您知道最大的产品是使用每组中最大因子的产品。
此外,每个其他产品都可以通过从一个较大的产品开始,然后减少恰好在一组中选择的因子来形成。
这导致了一个简单的搜索:
将最大的产品放入最大优先级队列。
重复k次:
一个。从优先级队列中移除最大的产品 p
b.对于每个集合的数字小于 p 中选择的集合, 通过将该数字减少到该集合中的下一个较低的数字来生成乘积。如果以前从未见过此选择的因素,则将其添加到优先级队列中。
产品会按降序从队列中移除,所以你取出的第k个是第k大的。
复杂度约为 N*(k log kN),具体取决于您实现事物的方式。
请注意,可能有多种方法可以选择产生相同产品的因素。该解决方案将这些方式视为不同的产品,即,在找到第 k 个最大的时计算每种方式。这可能是也可能不是你想要的。
【讨论】:
您能否确认您在最大和最小之间没有弄错?我可能会感到困惑,但“产品将按递增顺序从队列中移除,因此您取出的第 k 个是第 k 个最大的。” 听起来很可疑 我做到了。固定:) "如果之前没有出现过,则将其添加到优先级队列中。" - 这不太对,因为即使产品与以前的产品重复,您仍然需要继续搜索该产品。 @user2357112supportsMonica。是的,我将每个不同的元素选择视为不同的,而不是每个实际的产品价值。 这里的 N 是什么,是 3^n 的顺序(n 取自问题)?【参考方案2】:要将前面的讨论放入代码中,我们可以执行以下操作:
import operator
from functools import partial, reduce
import heapq
def prod_by_data(tup, data):
return reduce(operator.mul, (datum[t] for t, datum in zip(tup, data)), 1)
def downset(tup):
return [
tuple(t - (1 if j == i else 0) for j, t in enumerate(tup))
for i in range(len(tup))
if tup[i] > 0
]
data = [
[1, 2, 3],
[4, 2, 1],
[8, 1, 3],
[1, 1, 2],
]
data = [sorted(d) for d in data]
prod = partial(prod_by_data, data=data)
k_smallest = [tuple(len(dat) - 1 for dat in data)]
possible_k_smallest = []
while len(k_smallest) < 10:
new_possible = sorted(downset(k_smallest[-1]), key=prod, reverse=True)
possible_k_smallest = heapq.merge(possible_k_smallest, new_possible, key=prod, reverse=True)
k_smallest.append(next(possible_k_smallest))
print(k_smallest)
print([prod(tup) for tup in k_smallest])
我们维护一堆最小的元素。在我们弹出最小的元素后,我们需要检查所有元素是否向下(在一个位置上完全不同的元组),因为这些元组可能是下一个最小的元素。
我们看到我们查看了k - 1
次,每次使用本身为 O(n) 的键对 O(n) 个元素进行排序。由于 key 这应该使排序采用 O(n^2) 而不是 O(n log n)。 heapq
是惰性的,因此从它弹出实际上是 O(k)。初始排序和准备也应该是 O(n)。总的来说,我认为这使得一切都 O(k n^2)。
【讨论】:
嘿,请注意;math.prod
is a thing as of 3.8,因此您不必再使用 functools.reduce
+ operator.mul
从头开始重新发明它。
谢谢,很高兴知道。我想升级的另一个原因,哈哈。以上是关于如何在指数级大列表中找到第 k 个最大的元素?的主要内容,如果未能解决你的问题,请参考以下文章