我试图找出在某些数据聚合上计算top-k查询的最佳方法,比如说一个数组。我曾经认为最好的方法是运行数组并维护一个大小为k的堆或平衡二叉树,利用它来计算top-k值。现在,我已经遇到了选择算法,据说运行得更快。我理解选择算法是如何工作的以及如何实现它,我对它如何在O(n)中运行感到有点困惑。我觉得为了让它在O(n)中运行你必须非常幸运。如果你继续选择一个随机的枢轴点并围绕它进行分区,那么很可能就是你最终基本上对几乎整个数组进行排序然后绊倒你的第k个索引。是否有任何优化,例如可能没有选择随机数据?或者我在大多数情况下保持堆/树方法足够好。
答案 0 :(得分:1)
你在谈论的是quickselect, also known as Hoare's selection algorithm。
它的平均案例性能有O(n)
,但其最差情况表现为O(n2)
。
与quicksort一样,quickselect具有良好的平均性能,但对所选的枢轴敏感。如果选择好的枢轴,意味着一致地减少给定分数的搜索集,那么搜索集的大小以指数方式减小并且通过归纳(或几何系列的求和)可以看出性能是线性的,因为每个步骤是线性的并且总时间是一个恒定的时间(取决于搜索集减少的速度)。但是,如果始终选择坏的枢轴,例如每次只减少一个元素,则最坏情况的性能是二次的:
O(n2)
。
在选择枢轴方面:
最简单的解决方案是选择一个随机数,产生almost certain线性时间。确定性地,可以使用3中值枢轴策略(如在quicksort中),这在部分排序的数据上产生线性性能,这在现实世界中是常见的。然而,人为的序列仍然会导致最坏情况的复杂性; David Musser描述了一个“中位数为3的杀手”序列,允许攻击该策略,这是他的introselect算法的一个动机。
即使在最坏的情况下,通过使用更复杂的枢轴策略,也可以确保线性性能;这是在median of medians算法中完成的。然而,计算枢轴的开销很高,因此通常不会在实践中使用。可以将基本的快速选择与中位数的中位数结合起来作为后退,以获得快速平均案例表现和线性最坏情况表现;这是在introselect中完成的。
(引自Wikipedia)
因此,您很可能通过随机支点获得O(n)
表现,但是,如果k
很小且n
很大,或者您只是不太可能,那么{ {1}}使用大小O(n log k)
堆或BST的解决方案可能胜过此。
我们不能肯定地告诉你哪一个会更快 - 它取决于(1)确切的实现,(2)它运行的机器,(3)k
的确切大小和n
,最后是(4)实际数据。 k
解决方案应该足以满足大多数用途。