计算滑动窗口中位数的非平凡算法


25

我需要计算运行中位数:

  • 输入: ,,向量。k x 1x 2x nnk(x1,x2,,xn)

  • 输出:向量,其中是的中位数。ÿ X X + 1... X + ķ - 1(y1,y2,,ynk+1)yi(xi,xi+1,,xi+k1)

(不作弊;我希望有确切的解决方案。元素是大整数。)xi

有一个简单的算法可以维护大小为的搜索树;总运行时间为。(这里的“搜索树”是指一些有效的数据结构,支持对数时间内的插入,删除和中位数查询。)O n log k kO(nlogk)

但是,这对我来说似乎有点愚蠢。我们将有效地学习所有大小为窗口内的所有订单统计信息,而不仅仅是中位数。此外,这在实践中不太吸引人,特别是如果大(大型搜索树趋于缓慢,内存消耗的开销不平凡,缓存效率通常较差等)。ķkk

我们可以做得更好吗?

是否存在下界(例如,对于比较模型而言,平凡的算法是否渐近最优)?


编辑: David Eppstein为比较模型提供了一个很好的下界!我想知道是否有可能做一些比平凡算法更聪明的事情?

例如,我们可以沿着这些路线做些什么:将输入向量除以大小为部分;对每个部分进行排序(跟踪每个元素的原始位置);然后使用分段排序的向量在没有任何辅助数据结构的情况下有效地找到运行中值?当然,它仍然是,但是在实践中,对数组进行排序往往比维护搜索树要快得多。O n log k kO(nlogk)


编辑2: Saeed想了解为什么我认为排序比搜索树操作更快的一些原因。这是非常快速的基准,对于,:k=107n=108

  • ≈8s:对每个包含元素的向量进行排序n/kk
  • ≈10秒:对带有元素的向量进行排序n
  • ≈80s:大小为的哈希表中的插入和删除ķnk
  • ≈390s:大小为的平衡搜索树中的插入和删除ķnk

哈希表仅用于比较;在此应用程序中没有直接使用。

总而言之,我们在排序和平衡搜索树操作的性能上几乎相差50倍。如果我们增加,情况会变得更糟。k

(技术细节:数据= 32位随机整数。计算机=典型的现代笔记本电脑。测试代码使用C ++编写,使用标准库例程(std :: sort)和数据结构(std :: multiset,std ::我使用了两种不同的C ++编译器(GCC和Clang)以及两种不同的标准库实现(libstdc ++和libc ++)。传统上,std :: multiset被实现为高度优化的红黑树。)


1
我认为您无法改善。原因是,如果您查看窗口,就永远不能排除任何数字作为未来窗口的中位数。这意味着在任何时候您都必须在数据结构中至少保留整数,并且似乎更新时间不会少于日志时间。X x t + k 1 x t + knlogkxt,...,xt+k1kxt+k2,...,xt+k1k2
RB

对我来说,您的微不足道的算法似乎是而不是,我是否误解了?而且我认为正因为如此,您在使用大遇到了问题,否则对数因子在实际应用中就算不上什么,该算法也没有大的隐藏常数。ø Ñ 日志ķ ķO((nk)klogk)O(nlogk)k
2014年

@Saeed:在平凡的算法中,您要一一处理元素;在步骤您将添加到搜索树,并且(如果)还从搜索树中删除。这是n个步骤,每个步骤都需要O log k 时间。x i i > k x i kixii>kxiknO(logk)
Jukka Suomela 2014年

因此,您的意思是说您拥有平衡的搜索树而不是随意的搜索树?
2014年

1
@Saeed:请注意,在我的基准测试中,我什至没有尝试找到中位数。我只是在大小为k的搜索树中进行了插入和n次删除,并且保证了这些操作将花费O log k 时间。您只需要接受与排序相比,搜索树操作在实践中非常慢。如果您尝试编写一种排序算法,可以通过向平衡的搜索树中添加元素来工作,那么您将很容易看到这一点-它当然可以在O n log n 时间内工作,但是在实践中会很慢,而且浪费很多的记忆。nnkO(logk)O(nlogn)
Jukka Suomela 2014年

Answers:


32

这是排序的下限。给定一个要排序的长度为n的输入集,为您的运行中位数问题创建一个输入,该输入由n - 1个拷贝组成,该拷贝的数量小于S的最小值,然后是S本身,然后是n - 1个拷贝的数大于S的最大值,并设置k = 2 n - 1。此输入的运行中位数与S的排序顺序相同。Snn1小号小号n1个小号k=2n1个小号

因此,在计算的比较模型中,需要时间。如果您的输入是整数,并且使用整数排序算法,则可能会做得更好。Ω(n日志ñ


6
这个答案的确使我想知道相反是否也成立:给定有效的排序算法,我们可以获得有效的运行中位数算法吗?(例如,高效的整数排序算法是否意味着整数的有效运行中值算法?还是IO高效的排序算法提供了IO高效的运行中值算法?)
Jukka Suomela 2014年

1
再次感谢您的回答,它确实使我走上了正确的道路,并为基于排序的中值滤波算法提供了灵感!最终,我找到了1991年的论文,提出的论点与您在此处给出的论点基本相同,而Pat Morin给出了2005年另一篇相关论文的指点。参见参考。[6]和[9] 在这里
Jukka Suomela 2014年

9

编辑:现在在这里提供此算法:http : //arxiv.org/abs/1406.1717


是的,要解决此问题,只需执行以下操作:

  • 排序向量,每个向量包含k个元素。ñ/ķķ
  • 进行线性时间后处理。

大致来说,想法是这样的:

  • 考虑两个相邻的输入块b,它们都具有k个元素;让元件是一个1一个2一个ķb 1b 2b k在输入向量x中出现的顺序。一种bķa1,a2,...,akb1,b2,...,bkx
  • 对这些块进行排序,并了解块中每个元素的等级。
  • 使用前任/后继指针增强向量b,以便通过跟随指针链,我们可以按递增顺序遍历元素。这样,我们就构造了双链表a b abab
  • 一个接一个,删除从链表所有元素,在外观上的相反的顺序b ķb ķ - 1b 1。每当我们删除一个元素时,请记住在删除时它的后继者和前任者bbk,bk1,...,b1
  • 现在,维护分别指向列表a 'b '的 “中间指针” q。将p初始化为a '的中点,并将q初始化为空列表b '的尾部。pqabpaqb
  • 对于每个i

    • 从列表a '中删除(这是O 1 次,只需从链接列表中删除)。比较一个与元素指向的p,看看我们之前或之后删除paiaO(1)aipp
    • 放回到列表b '的原始位置(这是O 1 时间,我们记住了b i的前任和后继)。将b iq指向的元素进行比较,看看是否在q之前或之后添加了元素。bibO(1)bibiqq
    • 更新指针q使得所述接合列表中值一个b '是无论是在pq。(这是Ø 1 时间,只要按照链表一个或两个步骤来解决所有问题。我们会不断的有多少项目是前/后跟踪pq每个列表中,我们将保持不变,这两个pq指向尽可能接近中位数的元素。)pqabpqO(1)pqpq

链表只是索引的元素数组,因此它们是轻量级的(除了内存访问的位置很差之外)。k


这是示例实现和基准:

以下是运行(时间的曲线图):n2106

  • 蓝色=排序+后处理,O(nlogk)
  • 绿色=维护两个堆,从https://github.com/craffel/median-filter实现O(nlogk)
  • 红色=维护两个搜索树O(nlogk)
  • 黑色=维持排序的向量O(nk)
  • X轴=窗口大小()。k/2
  • Y轴=运行时间(以秒为单位)。
  • 数据=来自各种分布的32位整数和随机64位整数。

运行时间


3

考虑到大卫的局限性,您不太可能做得更好,但有更好的输出敏感算法。具体而言,如果结果中位数为,则可以在时间O n log m + m log n )中解决问题。mO(nlogm+mlogn)

为此,将平衡二叉树替换为平衡二叉树,该平衡二叉树仅包含过去的中值元素,再在每对先前中值之间添加两个斐波那契堆(每个方向一个),再加上计数,这样我们就可以找到哪个斐波那契堆按顺序包含特定元素。永远不要删除元素。插入新元素时,我们可以在时间内更新数据结构。如果新计数表明中位数在斐波那契堆之一中,则需要额外的O log n 才能将新中位数拉出。这个O log n O(logm)O(logn)O(logn) 每个中位数只发生一次电荷。

如果有一种干净的删除元素的方式而又不损害斐波那契堆的复杂性,我们将降至,但是我不确定这是否可行。O(nlogm+mlogk)


糟糕,这无法按书面要求执行,因为如果您不删除元素,则计数将不会反映新窗口。我不确定是否可以解决,但是如果有办法,我会留下答案。
杰弗里·欧文2014年

因此,我认为,如果从斐波那契堆中删除节点,该算法实际上可能会花费,因为斐波那契堆深度只有在调用delete-min时才会增加。考虑到删除分钟调用的次数,有人知道斐波那契堆复杂性的好界限吗?O(nlogm)
杰弗里·欧文

旁注:问题尚不清楚,底层数据结构未定义,我们只是知道一些非常模糊的内容。您想如何改善一些您不了解的东西?您想如何比较您的方法?
2014年

1
对于未完成的工作,我深表歉意。我已经问过要在此处解决此问题所需的具体问题:cstheory.stackexchange.com/questions/21778/…。如果您认为合适,我可以删除此答案,直到解决第二个问题。
杰弗里·欧文2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.