以前の質問を読んだことのある人は、クイックソートとクイック選択の理解と実装、およびその他のいくつかの基本的なアルゴリズムの私の仕事について知っています。
クイック選択は、並べ替えられていないリストでk番目に小さい要素を計算するために使用されます。この概念は、並べ替えられていないリストで中央値を見つけるためにも使用できます。
今回は、running medianを計算するための効率的な手法を考案する手助けが必要です。リストが変更されるたびに再計算する必要があるため、quickselectは適切な選択ではありません。クイックセレクトは毎回再起動する必要があるため、以前に実行された計算を利用することはできません。そのため、(おそらく)同様であるが、中央値の実行の領域でより効率的な別のアルゴリズムを探しています。
ストリーミング中央値 は、2つのヒープを使用して計算されます。現在の中央値以下のすべての数値は左側のヒープにあり、最大数がヒープのルートになるように配置されています。現在の中央値以上のすべての数値は右のヒープにあり、最小の数値がヒープのルートになるように配置されています。現在の中央値に等しい数値は、どちらのヒープにもあることに注意してください。 2つのヒープ内の数のカウントは、1を超えることはありません。
プロセスが開始すると、2つのヒープは最初は空です。入力シーケンスの最初の数値はヒープの1つに追加されます。どちらが問題であっても、最初のストリーミング中央値として返されます。次に、入力シーケンスの2番目の数値が他のヒープに追加されます。右ヒープのルートが左ヒープのルートよりも小さい場合、2つのヒープが交換され、2つの数値の平均が2番目のストリーミングとして返されます。中央値。
次に、メインのアルゴリズムが始まります。入力シーケンスの後続の各数値は現在の中央値と比較され、現在の中央値よりも小さい場合は左ヒープに追加され、現在の中央値よりも大きい場合は右ヒープに追加されます。入力数が現在の中央値と等しい場合、カウントが小さい方のヒープに追加されるか、同じカウントの場合は任意のヒープに追加されます。これにより2つのヒープのカウントが1以上異なる場合、大きいヒープのルートが削除され、小さいヒープに挿入されます。次に、現在の中央値は、カウントが異なる場合は大きいヒープのルートとして計算され、同じサイズの場合は2つのヒープのルートの平均として計算されます。
SchemeのコードとPythonはmy blog で入手できます。
Jeff McClintockの推定中央値。 2つの値のみを保持する必要があります。この例では、サンプル値の配列(CPU消費)を反復処理します。中央値の推定値に比較的速く(約100サンプル)収束するようです。アイデアは、各反復で、一定のレートで入力信号に向かって中央値インチです。レートは、中央値を推定する大きさによって異なります。中央値の大きさの推定値として平均を使用して、中央値の各増分のサイズを決定します。中央値を約1%に正確にする必要がある場合は、0.01 *平均のステップサイズを使用します。
float median = 0.0f;
float average = 0.0f;
// for each sample
{
average += ( sample - average ) * 0.1f; // rough running average.
median += _copysign( average * 0.01, sample - median );
}
1つの解決策は、 順序統計ツリー を維持し、シーケンスの各要素を順番に挿入してから、ツリー内の要素の中央値を計算することです。
これは、挿入ごとにO(lgn)時間がかかり、O(lgn)中央値あたりの時間、合計O(nlgn)時間とO(n)スペース。
以下は、ソートされたリスト内のインデックスでクエリする機能を提供するC++バランスツリー構造です。すべての値をソートされた順序で維持するため、これは2ヒープアプローチほど効率的ではありませんが、柔軟性がいくらか追加されます。たとえば、四分位数を実行することもできます。
template <typename T>
class Node
{
public:
T key;
Node* left;
Node* right;
size_t size;
Node(T k) : key(k)
{
isolate();
}
~Node()
{
delete(left);
delete(right);
}
void isolate()
{
left = NULL;
right = NULL;
size = 1;
}
void recount()
{
size = 1 + (left ? left->size : 0) + (right ? right->size : 0);
}
Node<T>* rotateLeft()
{
Node<T>* c = right;
Node<T>* gc = right->left;
right = gc;
c->left = this;
recount();
c->recount();
return c;
}
Node<T>* rotateRight()
{
Node<T>* c = left;
Node<T>* gc = left->right;
left = gc;
c->right = this;
recount();
c->recount();
return c;
}
Node<T>* balance()
{
size_t lcount = left ? left->size : 0;
size_t rcount = right ? right->size : 0;
if((lcount + 1) * 2 < (rcount + 1))
{
size_t lcount2 = right->left ? right->left->size : 0;
size_t rcount2 = right->right ? right->right->size : 0;
if(lcount2 > rcount2)
right = right->rotateRight();
return rotateLeft();
}
else if((rcount + 1) * 2 <= (lcount + 1))
{
size_t lcount2 = left->left ? left->left->size : 0;
size_t rcount2 = left->right ? left->right->size : 0;
if(lcount2 < rcount2)
left = left->rotateLeft();
return rotateRight();
}
else
{
recount();
return this;
}
}
Node<T>* insert(Node<T>* newNode)
{
if(newNode->key < key)
{
if(left)
left = left->insert(newNode);
else
left = newNode;
}
else
{
if(right)
right = right->insert(newNode);
else
right = newNode;
}
return balance();
}
Node<T>* get(size_t index)
{
size_t lcount = left ? left->size : 0;
if(index < lcount)
return left->get(index);
else if(index > lcount)
return right ? right->get(index - lcount - 1) : NULL;
else
return this;
}
Node<T>* find(T k, size_t start, size_t* outIndex)
{
if(k < key)
return left ? left->find(k, start, outIndex) : NULL;
else if(k > key)
return right ? right->find(k, left ? start + left->size + 1 : start + 1, outIndex) : NULL;
else
{
if(outIndex)
*outIndex = start + (left ? left->size : 0);
return this;
}
}
Node<T>* remove_by_index(size_t index, Node<T>** outNode)
{
size_t lcount = left ? left->size : 0;
if(index < lcount)
left = left->remove_by_index(index, outNode);
else if(index > lcount)
right = right->remove_by_index(index - lcount - 1, outNode);
else
{
*outNode = this;
size_t rcount = right ? right->size : 0;
if(lcount < rcount)
return left ? right->insert(left) : right;
else
return right ? left->insert(right) : left;
}
return balance();
}
Node<T>* remove_by_value(T k, Node<T>** outNode)
{
if(k < key)
{
if(!left)
throw "not found";
left = left->remove_by_value(k, outNode);
}
else if(k > key)
{
if(!right)
throw "not found";
right = right->remove_by_value(k, outNode);
}
else
{
*outNode = this;
size_t lcount = left ? left->size : 0;
size_t rcount = right ? right->size : 0;
if(lcount < rcount)
return left ? right->insert(left) : right;
else
return right ? left->insert(right) : left;
}
return balance();
}
};
template <typename T>
class MyReasonablyEfficientRunningSortedIndexedCollection
{
private:
Node<T>* root;
Node<T>* spare;
public:
MyReasonablyEfficientRunningSortedIndexedCollection() : root(NULL), spare(NULL)
{
}
~MyReasonablyEfficientRunningSortedIndexedCollection()
{
delete(root);
delete(spare);
}
void insert(T key)
{
if(spare)
spare->key = key;
else
spare = new Node<T>(key);
if(root)
root = root->insert(spare);
else
root = spare;
spare = NULL;
}
void drop_by_index(size_t index)
{
if(!root || index >= root->size)
throw "out of range";
delete(spare);
root = root->remove_by_index(index, &spare);
spare->isolate();
}
void drop_by_value(T key)
{
if(!root)
throw "out of range";
delete(spare);
root = root->remove_by_value(key, &spare);
spare->isolate();
}
T get(size_t index)
{
if(!root || index >= root->size)
throw "out of range";
return root->get(index)->key;
}
size_t find(T key)
{
size_t outIndex;
Node<T>* node = root ? root->find(key, 0, &outIndex) : NULL;
if(node)
return outIndex;
else
throw "not found";
}
size_t size()
{
return root ? root->size : 0;
}
};
ローリングウィンドウの中央値のアルゴリズム:
中央値は、中央の値をそこから取得する、並べ替えられた配列です。
シンプルなローリング実装は、queue(dqueue)とsorted_array(任意の実装、バイナリツリー、skiparray)を使用します。
d_queueは配列であり、配列の前面から末尾にプッシュしてシフト(ポップ)できます。
sorted_arrayは、バイナリ検索を使用して見つかった位置に順序で挿入する配列です。
キュー(先入れ先出し配列)を使用して追加された値の順序を追跡し、キューが必要なサイズよりも長い場合に、中央値配列から削除するアイテムを特定しました。日時または実行中のインデックスによって要素を落とすには、別のキューを追加して、最初の要素が古すぎることを確認し、両方のキューから最初の値を削除するかどうかを決定できます。
中央値を効率的に計算するために、ソートされた配列手法を使用します。ソートされた場所に新しいアイテムを挿入するときなので、配列は常にソートされます。
挿入:
削除:
中央値にするには:
#include<cstdio>
#include<iostream>
#include<queue>
#include <vector>
#include <functional>
typedef priority_queue<unsigned int> type_H_low;
typedef priority_queue<unsigned int, std::vector<unsigned int>, std::greater<unsigned int> > type_H_high;
size_t signum(int left, int right) {
if (left == right){
return 0;
}
return (left < right)?-1:1;
}
void get_median( unsigned int x_i, unsigned int &m, type_H_low *l, type_H_high *r) {
switch (signum( l->size(), r->size() )) {
case 1: // There are more elements in left (max) heap
if (x_i < m) {
r->Push(l->top());
l->pop();
l->Push(x_i);
} else {
r->Push(x_i);
}
break;
case 0: // The left and right heaps contain same number of elements
if (x_i < m){
l->Push(x_i);
} else {
r->Push(x_i);
}
break;
case -1: // There are more elements in right (min) heap
if (x_i < m){
l->Push(x_i);
} else {
l->Push(r->top());
r->pop();
r->Push(x_i);
}
break;
}
if (l->size() == r->size()){
m = l->top();
} else if (l->size() > r->size()){
m = l->top();
} else {
m = r->top();
}
return;
}
void print_median(vector<unsigned int> v) {
unsigned int median = 0;
long int sum = 0;
type_H_low H_low;
type_H_high H_high;
for (vector<unsigned int>::iterator x_i = v.begin(); x_i != v.end(); x_i++) {
get_median(*x_i, median, &H_low, &H_high);
std::cout << median << std::endl;
}
}