【问题标题】:Algorithm for Fast Median Update快速中值更新算法
【发布时间】:2013-06-09 18:13:05
【问题描述】:

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

这里发生的一件好事(如果不是有点难看的话)是:因为您将迭代器向前移动两次(并且安全地,我可能会添加,尽管代价是两次比较),当@987654335达到@,我们将处于正确的(中值)值。如果有奇数个元素,M 就是那个样本,如果没有,它只是这个元素和旧(推出)中位数的平均值。因为奇数和偶数交替出现,所以旧的或新的M 实际上会在集合中。这个推理是正确的,是吗?

如果您认为我的 O(3n) 方法是垃圾/您的方法要好得多,则无需评论它;我只是建议它作为一个起点。

【问题讨论】:

  • 你当然可以在 O(log(n)) 时间内完成。将元素添加到平衡二叉树并选择第 k 个最大元素都是 O(log(n)) 时间操作。对于更有趣的事情,可以尝试 2 个堆,一个用于 n/2 个最大的元素,一个用于最小的元素。
  • @MarcGlisse:看我的回答,你不必搜索第 k 个大元素,因为你已经知道它或者第 (k+1) 或 (k-1) 个最大的元素元素:-)
  • @SauceMaster 似乎您正在跟踪列表中出现次数最多的元素。这不是中位数。
  • @ArneMertz:哈哈,是的。那绝对是模式。对不起!

标签: c++ algorithm mean median forward-list


【解决方案1】:

您可以将数组拆分为两个 树,大小相等,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),则交换它们的顶部。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 2014-12-21
      • 2016-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-19
      • 2015-03-12
      • 2012-05-30
      • 1970-01-01
      相关资源
      最近更新 更多