快速中位数更新算法

Algorithm for Fast Median Update

本文关键字:算法 更新 中位数      更新时间:2023-10-16

假设在某个时间点,您有一个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 }});

顺便说一句,我很好奇是否有人有更好(更有效/优雅(的方法。欢迎抱怨。

所以,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( 方法/您的方法要好得多,则无需评论它;我只是建议将其作为一个起点。

您可以使用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;
  }
};

我会把擦除元素留给你作为练习;-(

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

请注意,如果元素数是偶数,则中位数 = top(S(+top(I(,如果奇数,则堆中的一个元素比另一个元素大,中位数位于较大的元素之上。

完成此操作后,更新中位数很简单,您应该将元素添加到其中一个堆中,如果 top(S( 小于 top(I(,则交换它们的顶部。