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

Algorithm 百分位数重复计算的快速算法?,algorithm,optimization,data-structures,percentile,Algorithm,Optimization,Data Structures,Percentile,在一个算法中,每当我添加一个值时,我必须计算数据集的长度。现在,我正在这样做: 获取值x 在后面已排序的数组中插入x 向下交换x直到数组排序 读取位置array[array.size*3/4]处的元素 点3是O(n),其余的是O(1),但这仍然非常慢,特别是当数组变大时。有没有办法优化这个 更新 谢谢你,尼基塔!由于我使用C++,这是最容易实现的解决方案。代码如下: template<class T> class IterativePercentile { public: ///

在一个算法中,每当我添加一个值时,我必须计算数据集的长度。现在,我正在这样做:

  • 获取值
    x
  • 在后面已排序的数组中插入
    x
  • 向下交换
    x
    直到数组排序
  • 读取位置
    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;
    };
    
    模板
    类迭代百分位数{
    公众:
    ///百分位数必须在[0,1]范围内(
    迭代百分位数(双百分位数)
    :_百分位(百分位)
    { }
    //在O(log(n))中添加一个数字
    无效添加(常量T&x){
    如果(_lower.empty()| | x size_lower){
    //从低到高
    std::pop_heap(_lower.begin(),_lower.end(),std::less());
    _上。推回(_下。回());
    std::push_heap(_upper.begin(),_upper.end(),std::greater());
    _放低。弹回();
    }否则如果(\u lower.size()
    您可以使用二进制搜索在O(logn)中找到正确的位置。但是,向上移动数组仍然是O(n)。

    您可以用两个数组来完成。不确定是否有“人为”解决方案,但此解决方案提供了
    O(logn)
    时间复杂性和堆也包含在大多数编程语言的标准库中

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

  • 添加元素。
  • 查看新元素
    x
    是否为0
    array[array.size*3/4]=min(B)

    简单即可

    此树的平衡版本支持O(logn)时间插入/删除和按等级访问。因此,您不仅可以获得75%的百分位数,还可以获得66%或50%的百分位数或任何您需要的值,而无需更改代码

    如果频繁访问75%的百分位数,但插入频率较低,则在插入/删除操作期间始终可以缓存75%的百分位数元素


    大多数标准实现(如Java的TreeMap)都是顺序统计树。

    如果您有一组已知的值,则以下内容将非常快速:

    创建一个大整数数组(即使是字节也可以),其元素数等于数据的最大值。 例如,如果t的最大值为100000,则创建一个数组

    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 :)
    
    现在将百分位数计算为

    int sum = 0, i = 0;
    while (sum < 0.9*set_of_values.length) {
      sum += index[i++];
    }
    
    return i;
    
    int和=0,i=0;
    while(总和<0.9*设置值长度){
    总和+=指数[i++];
    }
    返回i;
    

    也可以考虑使用TeaMeP而不是数组,如果这些值不符合这些限制。

    < P>这是一个JavaScript解决方案。复制粘贴在浏览器控制台中,它工作。<代码> $得分<代码>包含列表,并且<代码>百分位数< /代码>给出列表的<代码>第n个百分位< /代码>。因此第七十五百分位是76.8和99%为87.9

    function get_percentile($percentile, $array) {
        $array = $array.sort();
        $index = ($percentile/100) * $array.length;
        if (Math.floor($index) === $index) {
             $result = ($array[$index-1] + $array[$index])/2;
        }
        else {
            $result = $array[Math.floor($index)];
        }
        return $result;
    }
    
    $scores = [22.3, 32.4, 12.1, 54.6, 76.8, 87.3, 54.6, 45.5, 87.9];
    
    get_percentile(75, $scores);
    get_percentile(90, $scores);
    

    如果你可以用一个近似的答案,你可以使用直方图而不是把整个值保存在内存中

    对于每个新值,将其添加到相应的bin中。 通过遍历存储箱并对计数求和计算第75个百分位,直到达到人口规模的75%。百分位值介于存储箱(您停止在)下限和上限之间

    这将提供O(B)复杂度,其中B是存储箱的计数,即
    范围大小/bin大小
    (根据您的用户情况使用
    bin大小


    我已经在JVM库中实现了这个逻辑:您可以将其用作参考。

    但是如何确定堆a是否变得太大?@Raze2dust堆a应该包含大约75%的元素。如果它的大小超过这个值,它就会变得太大。@Raze2dust如果您的意思是“如何获得堆大小”,它是一个O(1)操作:)我认为这个想法会奏效,但我认为有必要做一些修改。首先,其中一个堆上应该始终有您要查找的项目。这样,您就可以计算出给定数量的元素的每个堆的大小
    堆a=floor(n*.75)和堆B=ceil(n*.25)
    (在本例中)。接下来,当您添加一个项时,确定哪个堆需要增长。如果堆A需要增长,并且该项小于B的顶部,请将其添加到A。否则,请删除B的顶部,将其添加到A,然后将新项添加到B。(删除然后添加作为修改更有效)@Nikita-不,只是一些调整。定义应该增长的堆使添加操作稍微简单一些(您的添加可以执行3个O(logn)操作(添加、删除、添加)。我的建议是两个(修改、添加)在最坏的情况下。你选择哪个堆并不重要,但是选择一个小堆来存放物品将使堆的大小更接近,从而获得(可能微不足道的)性能提升。很好,我最近在一次采访中遇到了一个类似的问题。Nikita已经给出了我的答案。@Alexandru:类似!=相同:-)我认为这里不需要堆解决方案。它可能适用于:,但我认为它是一个mis应用程序。我认为:
    中存在未定义的行为:
    如果(_lower.empty()| | x@davide)计算顺序定义良好,如果
    _lower.empty()
    返回true,则右侧为否