那些阅读过我之前问题的人都知道我在理解和实施快速排序和快速选择方面的工作,以及其他一些基本算法。
Quickselect用于计算未排序列表中的第k个最小元素,此概念也可用于在未排序列表中查找中位数。
这一次,我需要帮助设计一种有效的技术来计算运行中位数,因为quickselect不是一个好选择,因为它需要在每次列表更改时重新计算。因为quickselect必须每次都重新启动,所以它无法利用先前的计算,所以我正在寻找一种类似(可能)但在运行中位数方面更有效的不同算法。
答案 0 :(得分:41)
使用两个堆计算streaming median。小于或等于当前中位数的所有数字都在左堆中,其排列使得最大数量位于堆的根处。所有大于或等于当前中位数的数字都在右侧堆中,其排列使得最小数量位于堆的根部。请注意,等于当前中位数的数字可以在任一堆中。两个堆中的数字计数从不相差超过1。
当进程开始时,两个堆最初是空的。输入序列中的第一个数字被添加到其中一个堆中,哪个并不重要,并作为第一个流中位数返回。然后输入序列中的第二个数字被添加到另一个堆中,如果右堆的根小于左堆的根,则交换两个堆,并且这两个数的平均值作为第二个流返回中位数。
然后主算法开始。输入序列中的每个后续数字与当前中位数进行比较,如果小于当前中位数,则添加到左侧堆中;如果大于当前中位数,则添加到右侧堆中;如果输入数字等于当前中位数,则将其添加到具有较小计数的任何堆中,或者如果它们具有相同计数则任意堆积。如果这导致两个堆的计数差异超过1,则删除较大堆的根并将其插入较小的堆中。然后将当前中位数计算为较大堆的根,如果它们的计数不同,或者两个堆的根的平均值,如果它们的大小相同。
我的blog提供了Scheme和Python中的代码。
答案 1 :(得分:14)
杰夫麦克林托克运行中位数估计。只需要保留两个值。 此示例迭代一组采样值(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 );
}
答案 2 :(得分:6)
一种解决方案是保持order statistic tree,依次插入序列的每个元素,然后计算树中元素的中位数。
每次插入需要O(lg n )时间,每个中位数需要O(lg n )时间,总计O( n lg n )时间加上O( n )空间。
答案 3 :(得分:1)
这是一个C ++平衡树结构,它提供了按排序列表中的索引进行查询的功能。由于它按排序顺序维护所有值,因此这不像双堆方法那样有效,但它提供了一些额外的灵活性。例如,它也可以给你一个运行的四分位数。
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;
}
};
答案 4 :(得分:1)
滚动窗口中位数算法:
median是一个排序数组,你从中取出中间值。
简单滚动实现是使用队列(dqueue)和sorted_array(任何实现,二叉树,skiparray)。
d_queue是一个数组,你可以从数组的前面推到尾部并移动(弹出)。
sorted_array是一个数组,您可以按顺序在使用二分查找找到的位置插入。
我使用队列(先进先出数组)来跟踪添加值的顺序,以便在队列长度超过所需大小时知道要从中间数组中删除哪些项目。要按日期时间或某些运行索引来减少元素,可以添加另一个队列并检查第一个元素是否过旧,并决定是否从两个队列中删除第一个值。
为了有效地计算中值,我使用了排序数组技术。它是在您将新项目插入其排序位置时,因此数组始终排序。
插入内容:
删除:
要获得中位数:
答案 5 :(得分:0)
#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;
}
}