快速算法用于重复计算百分位数?

问题描述 投票:0回答:3

在算法中,我必须在添加一个值时计算数据集的第75个百分位数。现在我正在这样做:

获取值
    x
  1. 在后面已经分类的数组中insert
  2. x
  3. swap
    x
  4. 向下down直到排序数排序
  5. 在位置上阅读元素
    array[array.size * 3/4]
  6. 点3是O(n),其余的是O(1),但这仍然很慢,尤其是在数组变大的情况下。有什么方法可以优化?
    
update

thanks nikita!由于我使用的是C ++,这是最容易实现的解决方案。这是代码:

template<class T> class IterativePercentile { public: /// Percentile has to be in range [0, 1( IterativePercentile(double percentile) : _percentile(percentile) { } // Adds a number in O(log(n)) void add(const T& x) { if (_lower.empty() || x <= _lower.front()) { _lower.push_back(x); std::push_heap(_lower.begin(), _lower.end(), std::less<T>()); } else { _upper.push_back(x); std::push_heap(_upper.begin(), _upper.end(), std::greater<T>()); } unsigned size_lower = (unsigned)((_lower.size() + _upper.size()) * _percentile) + 1; if (_lower.size() > size_lower) { // lower to upper std::pop_heap(_lower.begin(), _lower.end(), std::less<T>()); _upper.push_back(_lower.back()); std::push_heap(_upper.begin(), _upper.end(), std::greater<T>()); _lower.pop_back(); } else if (_lower.size() < size_lower) { // upper to lower std::pop_heap(_upper.begin(), _upper.end(), std::greater<T>()); _lower.push_back(_upper.back()); std::push_heap(_lower.begin(), _lower.end(), std::less<T>()); _upper.pop_back(); } } /// Access the percentile in O(1) const T& get() const { return _lower.front(); } void clear() { _lower.clear(); _upper.clear(); } private: double _percentile; std::vector<T> _lower; std::vector<T> _upper; };

您可以使用两个

aps
来做到这一点。不确定是否有一个“人为人为”的解决方案,但是该解决方案提供了大多数编程语言的标准库中提供的时间复杂性和堆。

第一堆(堆A)包含最小的75%元素,另一个堆(堆B) - 其余(最大25%)。第一个具有最大的元素,第二个元素 - 最小。

algorithm optimization data-structures percentile
3个回答
40
投票
填充元素

请参阅新元素是否为O(logn)

。如果是这样,请将其添加到堆
x

,否则 - 堆

max(A)

。 现在,如果我们添加了
    A
  1. 堆堆,并且它变得太大(占元素的75%以上),我们需要从B(o(logn))中删除最大元素,然后将其添加到堆B(也o(logn)) 如果堆B变得太大,则类似。

找到“ 0.75中位数”


<= 
即将从A(或B中最小)获取最大元素。需要O(logn)或O(1)时间,具体取决于堆的实现。

eDit

正如dolphin
所指出的那样,我们需要精确指定每个n堆应有多大的(如果我们需要精确的答案)。例如,如果其余的是
x
A
,那么,对于每个
size(A) = floor(n * 0.75)

  1. 简单的排序统计树已经足够了。
  2. 该树的平衡版本支持O(logn)时间插入/删除和按等级访问。因此,您不仅获得了75%的百分点,而且还获得了66%或50%或不需要更改代码的任何内容。
如果您经常访问75%的百分位数,但只有少量插入次数,您总是可以在插入/删除操作期间缓存75%的百分位数元素。

最大的标准实现(例如Java的Treemap)是订单统计树。

如果您可以使用近似答案来完成,则可以使用直方图,而不是将整个值保持在内存中。
对于每个新值,将其添加到适当的垃圾箱中。 通过遍历垃圾箱并总结数量,直到达到人口规模的75%来计算百分位数。百分位数是在bin的(您停在)之间的低点之间的bin。 这将提供O(b)的复杂性,其中b是垃圾箱的计数,即size(B)

。 (使用
n > 0
适合您的用户案例)。 
我已经在JVM库中实现了此逻辑:
https://github.com/ibm/hbpe
您可以用作参考。

您可以使用二进制搜索在O(log n)中找到正确的位置。  但是,将阵列移动仍然是O(n)。
    


18
投票
如果您有一组已知的值,以下将非常快:

创建了大量整数(甚至会起作用),其元素数量等于您的数据的最大值。 例如,如果t的最大值为100,000,则创建一个数组 array[array.size * 3/4] = min(B)

现在迭代整个值,as

range_size/bin_size

现在计算百分位数AS


3
投票
如果值未确认这些限制,则您还可以考虑使用Treemap而不是数组。

here是一种JavaScript解决方案。在浏览器控制台中复制将其搭配起来。

int[] index = new int[100000]; // 400kb

包含分数列表,

for each (int t : set_of_values) {
  index[t]++;
}

// You can do a try catch on ArrayOutOfBounds just in case :)
为列表的cives。因此,第75个百分位数为76.8,而99%为87.9。
int sum = 0, i = 0;
while (sum < 0.9*set_of_values.length) {
  sum += index[i++];
}

return i;

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.