C++ 快速中值更新算法

C++ 快速中值更新算法,c++,algorithm,mean,median,forward-list,C++,Algorithm,Mean,Median,Forward List,假设在某个时间点,您有一组N数字,并且知道中间元素:M。现在,您得到了一个新值,X,因此您可能需要更新M。(或者更确切地说,您需要这样做,假设您处理的数字都是唯一的。而且,所有样本都是串行接收的,因此并发性没有问题。) 计算新的平均值很简单:取旧的平均值,加上X,乘以N,然后除以N+1。(通过检查N个元素的平均值是如何定义的,这一点很清楚。目前我不太担心数字。) 我的问题是:有人能提出一种创造性的/新颖的(或者可以证明是最优的)方法来解决更新中值的问题吗?我将在下面提供一个示例(我自己设计的简单

假设在某个时间点,您有一组
N
数字,并且知道中间元素:
M
。现在,您得到了一个新值,
X
,因此您可能需要更新
M
。(或者更确切地说,您需要这样做,假设您处理的数字都是唯一的。而且,所有样本都是串行接收的,因此并发性没有问题。)

计算新的平均值很简单:取旧的平均值,加上
X
,乘以
N
,然后除以
N+1
。(通过检查N个元素的平均值是如何定义的,这一点很清楚。目前我不太担心数字。)

我的问题是:有人能提出一种创造性的/新颖的(或者可以证明是最优的)方法来解决更新中值的问题吗?我将在下面提供一个示例(我自己设计的简单想法),并进行一些分析:

在这个示例中,我将使用一个
std::forward\u列表
,因为我最近在C++11中遇到了这个问题。在不丧失一般性的情况下,我将假设您的做法是正确的:维护迄今为止遇到的元素(类型T)的有序列表,
std::forward\u list sorted
tx时出现,只需使用以下工具将其折叠到位:

sorted.merge(std::forward_list<T> {{ x }});
这里发生的一件好事(如果不是很难看到的话)是:由于您将迭代器向前移动了两次(而且是安全的,我可能会添加,尽管代价是两次比较),当达到
end()
时,我们将处于适当的(中间)值。如果有奇数个元素,
M
就是这个样本,如果没有,它就是这个元素和旧的(被挤出的)中位数的平均值。因为奇数和偶数交替出现,所以旧的或新的
M
实际上将在集合中。这个推理是正确的,是吗


如果你认为我的O(3n)方法是垃圾/你的好得多,你就不必评论它;我只是建议将其作为一个起点。

您可以将数组拆分为两个大小相等的堆树,
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
14
\ /
4   2
\ /

5您可以使用
std::set
,而且插入到set不会使迭代器无效

如果
N
为奇数,则可以将迭代器
mIt
放在集合的中间元素上,如果
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;
  }
};
模板
类中间文件夹{
std::集合元素;
std::set::const_迭代器mIt;
公众:
T const&getmedia()const{return*mIt;}
无效插入(T常量和T){
if(elements.empty()){
mIt=元素。插入(t)。首先;
返回;
}
bool-smaller=std::less(t,getMedian());
布尔奇数=(elements.size()%2)==1;
if(!elements.insert(t).second)
return;//未插入
如果(奇数和更小)-麻省理工学院;
如果(!奇数&!更小)+mIt;
}
};

我将把删除元素作为练习留给您;-)

您当然可以在O(log(n))时间内完成它。向平衡二叉树添加元素和选择第k个最大元素都是O(log(n))时间操作。更有趣的是,可以尝试两个堆,一个用于n/2个最大的元素,一个用于最小的元素。@Marglisse:看我的答案,你不必搜索第k个larges元素,因为你已经知道它或者(k+1)第或(k-1)个第四大元素:-)@SauceMaster您似乎正在跟踪列表中出现次数最多的元素。这不是中位数。@ArneMertz:哈哈,是的。这绝对是一种模式。很抱歉
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;
  }
};