一尘不染

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

algorithm

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

  1. 获得价值 x
  2. 插入x后面已排序的数组中
  3. x向下交换直到对数组进行排序
  4. 读取位置上的元素 array[array.size * 3/4]

点3是O(n),其余点是O(1),但这仍然很慢,尤其是当数组变大时。有什么办法可以优化这个?

更新

谢谢妮基塔!由于我使用的是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;
};

阅读 309

收藏
2020-07-28

共1个答案

一尘不染

您可以使用两个堆来完成。不确定是否有一个“人为”的解决方案,但是这种解决方案具有O(logn)时间复杂性,并且大多数编程语言的标准库中也包含堆。

第一个堆(堆A)包含最小的75%元素,另一个堆(堆B)-其余(最大25%)。第一个元素在顶部的位置最大,第二个元素-最小的位置。

  1. 添加元素。

查看new元素x是否为<= max(A)。如果是,请将其添加到heap A,否则添加到heap B
现在,如果我们添加x到堆A并且它变得太大(容纳超过75%的元素),我们需要从A(O(logn))中删除最大的元素,然后将其添加到堆B(也是O(logn))中。
如果堆B太大,则类似。

  1. 发现“ 0.75中位数”

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

编辑
正如 Dolphin 所说,我们需要精确地指定每n个堆的大小(如果我们想要精确的答案)。例如,如果size(A) = floor(n * 0.75)size(B)是余数,则对于n > 0array[array.size * 3/4] = min(B)

2020-07-28