假设在某一时刻,您有一个包含N个数字并知道中位数M的集合。现在,您有一个新值X,可能需要更新M。(假设您处理的数字都是唯一的。此外,所有样本都是串行接收的,因此不存在并发问题。)
计算新平均值很简单:取旧平均值,加上X,乘以N,然后除以N + 1。(从检查N个元素的平均值如何定义可以清楚地看出这一点。目前我对数字不太担心。)
我的问题是:有人能提出一个创意/新颖(或者证明最优)的方法来解决更新中位数的问题吗?下面是一个例子(我自己设计的简单想法),带有一些分析:
在此示例中,我将使用std::forward_list,因为C++11是我最近遇到的。不失一般性,我假设您正在正确地进行操作:维护一个已排序的元素列表(类型T),std::forward_list sorted; 当T x;出现时,只需使用以下代码将其折叠到位:
这里发生的美好之处(虽然有些难以看清)在于:每次移动迭代器两次(并且安全,我可以补充说明,尽管需要付出两个比较的代价),当到达
如果您认为我的O(3n)方法很糟糕/您的方法要好得多,您不必评论它,我只是建议它作为一个起点。
计算新平均值很简单:取旧平均值,加上X,乘以N,然后除以N + 1。(从检查N个元素的平均值如何定义可以清楚地看出这一点。目前我对数字不太担心。)
我的问题是:有人能提出一个创意/新颖(或者证明最优)的方法来解决更新中位数的问题吗?下面是一个例子(我自己设计的简单想法),带有一些分析:
在此示例中,我将使用std::forward_list,因为C++11是我最近遇到的。不失一般性,我假设您正在正确地进行操作:维护一个已排序的元素列表(类型T),std::forward_list sorted; 当T x;出现时,只需使用以下代码将其折叠到位:
sorted.merge(std::forward_list<T> {{ x }});
顺便问一下,我很好奇是否有更好(更高效/优雅)的方法来实现这个。欢迎吐槽。
现在,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; }
}
这里发生的美好之处(虽然有些难以看清)在于:每次移动迭代器两次(并且安全,我可以补充说明,尽管需要付出两个比较的代价),当到达
end()
时,我们将到达适当的(中位数)值。如果元素数量为奇数,则M
就是那个样本,否则,它只是该元素和旧(推出的)中位数的平均值。因为奇偶数字交替出现,所以旧的或新的M
实际上都将在集合中。这种推理是正确的,对吗?如果您认为我的O(3n)方法很糟糕/您的方法要好得多,您不必评论它,我只是建议它作为一个起点。