如何使用堆找到线性时间内的数字中位数?

时间:2010-04-05 17:38:20

标签: algorithm heap time-complexity median

Wikipedia说:

  

选择算法:找到分钟,   最大值,最小值和最大值,中值,或   即使是第k个最大的元素也可以   使用堆在线性时间内完成。

所有这一切都表明它可以完成,而不是如何完成。

你能给我一些关于如何使用堆来完成这项工作的开始吗?

7 个答案:

答案 0 :(得分:21)

您可以使用min-max-median堆来查找恒定时间内的最小值,最大值和中值(并使用线性时间来构建堆)。您可以使用order-statistics树来查找第k个最小/最大值。 this paper on min-max heaps [pdf link]中描述了这两种数据结构。最小 - 最大堆是在最小堆和最大堆之间交替的二进制堆。

来自论文:min-max-median堆是具有以下属性的二进制堆:

1)所有元素的中位数都位于根

2)根的左子树是尺寸上限[((n-1)/ 2)]的最小 - 最大堆H1,其包含小于或等于中值的元素。右子树是大小为[((n-1)/ 2)]的最大最小堆Hr,只包含大于或等于中位数的元素。

本文接着解释如何构建这样的堆。

编辑:在更彻底地阅读论文时,似乎构建最小 - 最大 - 中值堆需要您首先找到中位数(FTA:“使用已知线性时间中的任何一个查找所有n个元素的中值算法“)。也就是说,一旦构建了堆,就可以通过维持左边的最小 - 最大堆和右边的最大最小堆之间的平衡来维持中值。 DeleteMedian用最大最小堆的最小值或最小最大堆的最大值(以保持平衡为准)替换root。

因此,如果您计划使用min-max-median堆来查找固定数据集的中位数,那么您就是SOL,但如果您在更改数据集上使用它,则可能。

答案 1 :(得分:4)

selection algorithms上查看此维基百科页面。特别是,请看BFPRT算法和Median of Medians算法。 BFPRT在概率上是线性的,并且以快速排序为模型;中位数中位数保证线性,但具有较大的常数因子,因此在实践中可能需要更长时间,具体取决于数据集的大小。

如果您只有几百或几千个元素可以从中选择中位数,我怀疑一个简单的快速排序然后直接索引是最简单的。

答案 2 :(得分:4)

可能有更好的算法,但这就是我的方法:

有两个存储桶和一个值。该值是中位数,两个桶“大于中位数”和“小于中位数”。对于数组中的每个元素x,重新平衡存储区,使big_bucketsmall_bucket的大小相差不超过1。当将物品从大水桶移动到小水桶时,他们首先必须通过中间值才能到达那里(也就是说,差异为2会成功地将元素从一个水桶推到下一个水桶 - 差异为1会推动一个元素从一个桶到中值。)在第一次通过数组时,值应该是你的中位数。

答案 3 :(得分:3)

也许它在问到原始问题时并不存在,但现在wiki有一个指向源的链接,这里是:http://ftp.cs.purdue.edu/research/technical_reports/1991/TR%2091-027.pdf

具体来说,请转到第17页,查看RSEL4的说明。他们在定理3.2中证明了这个第k选择算法的时间复杂度是O(k)。因此,你需要O(n)来构建堆,并使用额外的O(k)来找到第k个最小的项。

它并不像其他一些答案所暗示的那样直截了当

答案 4 :(得分:0)

如果您对堆数据结构有更多了解,您将很容易理解实际情况。堆结构可以在O(n)时间内构建,有最小堆和最大堆。 min heap root元素将为您提供最小的元素。 max heap root元素将为您提供max元素。只需构建堆,就可以找到最小值和最大值。对于中位数和第k个最大的相同的想法,在构建堆时,您可以通过查看树的左或右分支并通过保持恒定的内存量来存储元素编号来找到中值和第k个最大值。等

答案 5 :(得分:0)

将第一个整数存储在数组中并将计数器设置为1.然后遍历向量中的其余整数。如果数组中的当前整数与存储的整数相同,则计数器增加1,否则计数器减1。如果计数器达到零,则丢弃存储的整数并将其替换为数组中的当前整数。当你最终遍历所有整数时,你会留下一个候选人。然后,您需要再次遍历数组并计算候选项的出现次数,以验证这确实是一个支配者。

static int FindDominator(int[] arr)
{
int counter = 1;
int candidate = arr[0];
for(int i = 1; i < n; i++)
{
   if(arr[i] == candidate) counter++
    else 
   {
        counter--;
        if(counter == 0) { candidate = arr[i]; counter = 1; }
    }
}
counter = 0;
for(int i = 0;  i < n; i++)
{
    if(arr[i] == candidate) counter++;
}
if(counter > n / 2) return candidate;
else return -1;
}

答案 6 :(得分:-1)

显然,O(n)中的min和max很容易,不需要堆。

K'th最大可以相当简单地通过保持k值大小的前k个值到目前为止完成。运行时间为O(n * logk)。如果k是固定大小并且k <&lt;&lt; Ñ

我不认为中位数是可能的。只创建一个O(n)大小的堆需要O(n * logn)时间。

编辑:好的,在考虑了这个之后,IVlad是对的。您可以在O(n)中创建一个固定大小的堆。但是......这对他的中位问题没有帮助。线性堆创建技术仅生成有效堆作为其最终输出。进行n次插入的简单方法,在每一步之后产生有效的堆是O(n * logn)。

在我看来,使用堆来查找中位数需要使用那些运行子堆。例如,这里发布了一个答案(现在似乎已被删除),该答案链接到一篇博客文章,建议针对此问题的算法。它使用两个堆(较小的一半和较大的一半)跟踪运行中值,因为它只通过数据。这需要更慢,更天真的堆方法,因为它依赖于在插入和删除它们时保持有效堆。

是否有其他方法可以使用线性一次性堆创建技术找到中位数?