web-dev-qa-db-ja.com

パーセンタイルを繰り返し計算するための高速アルゴリズム?

アルゴリズムでは、値を追加するたびに、データセットの 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;
};
31
martinus

あなたは2つでそれを行うことができます ヒープ 。 「工夫された」解決策が少ないかどうかはわかりませんが、これはO(logn)時間計算量を提供し、ヒープはほとんどのプログラミング言語の標準ライブラリにも含まれています。

最初のヒープ(ヒープA)には最小の75%の要素が含まれ、別のヒープ(ヒープB)には残り(最大の25%)が含まれます。最初の要素は上部に最大の要素があり、2番目の要素は最小です。

  1. 要素を追加しています。

新しい要素xが<= max(A)であるかどうかを確認します。そうである場合は、ヒープAに追加し、そうでない場合は、ヒープBに追加します。
ここで、ヒープAにxを追加し、それが大きくなりすぎた場合(要素の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 > 0_について、array[array.size * 3/4] = min(B)

31
Nikita Rybak

単純な 注文統計ツリー で十分です。

このツリーのバランスの取れたバージョンは、O(logn)時間の挿入/削除とランクによるアクセスをサポートします。したがって、75%のパーセンタイルだけでなく、66%または50%なども取得できます。コードを変更せずに必要です。

75%パーセンタイルに頻繁にアクセスするが、挿入頻度は低い場合は、挿入/削除操作中にいつでも75%パーセンタイル要素をキャッシュできます。

ほとんどの標準的な実装(JavaのTreeMapなど)は、順序統計ツリーです。

14
Aryabhatta