假设在某个时间点,您有一组N
个数字,并且知道中间元素:M
。现在,您获得了一个新值X
,因此您可能需要更新M
。 (或者更确切地说,您需要,假设您正在处理的数字都是唯一的。此外,所有样本都是连续接收的,因此并发性没有问题。)
计算新均值非常简单:采用旧均值,添加X
,乘以N
,然后除以N + 1
。 (通过检查N元素的平均值是如何定义的,这一点很清楚。目前我并不太担心数字。)
我的问题是:任何人都可以提出更新中位数的创意/小说(或者可能是可证明最优的)方法吗?我将在下面提供一个示例(我自己设计的简单概念),并进行一些分析:
在这个示例中,我将使用std::forward_list
,因为C ++ 11是我最近遇到的地方。在不失一般性的情况下,我将假设您正确地采用这种方式:维护到目前为止遇到的元素(类型T)的有序列表,std::forward_list<T> sorted;
当出现T x;
时,使用以下方法将其折叠到位:
sorted.merge(std::forward_list<T> {{ x }});
顺便说一句,我很好奇是否有人有更好的(更有效/更优雅)的方法。欢迎Gripes。
所以,X
现在是sorted
的一部分,简而言之,这就是我的想法:
auto it = sorted.begin(), itend = sorted.end();
typename std::forward_list<T>::size_type count = std::distance(it, itend);
for (const auto &e : sorted) {
if (it == itend || ++it == itend) {
M = (count % 2) ? e : (e + M) / 2;
break;
} else { ++it; }
}
这里发生的好事(如果不是有点难以察觉)就是:因为你将每个元素向前移动迭代器两次(并且安全地,我可能会以两次比较为代价),{{达到1}},我们将处于正确的(中位数)值。如果有一个奇数个元素,end()
只是那个样本,如果没有,它只是这个元素的平均值和旧的(推出)中值。因为奇数和偶数交替,旧的或M
实际上将在集合中。这个推理是合理的,是吗?
如果你认为垃圾/你的垃圾好多了,你不需要评论我的O(3n)方法;我只是建议它作为一个起点。
答案 0 :(得分:7)
您可以将数组拆分为两个堆树,大小相等,I
最少部分或数组,S
最重要,并且它们的顶部包含最大和最小元素。假设数组1, 2, 4, 4, 5, 5, 7, 8, 8, 8
组织如下:
1 4
\ /
4 2
\ /
5 <--- I's top
5 <--- S's top
/ \
7 8
/ \
8 8
注意,如果元素的数量是偶数,那么中位数=顶部(S)+顶部(I),如果是奇数,则其中一个堆应该是比其他元素大的一个元素,并且中位数在更大的元素之上。
完成此操作后,更新中位数很简单,如果top(S)小于top(I),则应将元素添加到其中一个堆中并交换顶部。
答案 1 :(得分:5)
您可以使用std::set
,并且插入到集合的事实不会使迭代器无效。
如果mIt
为奇数,则可以将迭代器N
保持到集合的中值元素,如果N
是偶数,则可以将两个中值元素的左侧保持为。
让我们考虑一下插入元素时可能遇到的不同情况:
当N
为奇数时插入:如果插入的元素小于*mIt
,则旧的中位数变为两个新的中值元素的右侧,因此递减迭代器。如果它更大(或等于multiset
),一切都很好。
当N
为偶数时插入:如果插入的元素大于(或等于)*mIt
,则旧的右中位数变为中位数,因此增加迭代器。如果它更小,旧的左中位数变为中位数,一切都很好。
template <class T>
class MedianHolder {
std::set<T> elements;
std::set<T>::const_iterator mIt;
public:
T const& getMedian() const { return *mIt; }
void insert(T const& t) {
if (elements.empty()) {
mIt = elements.insert(t).first;
return;
}
bool smaller = std::less<T>(t,getMedian());
bool odd = (elements.size() % 2) == 1;
if (!elements.insert(t).second)
return; //not inserted
if (odd && smaller) --mIt;
else if (!odd && !smaller) ++mIt;
}
};
我会把擦除元素作为练习留给你; - )