计算十亿个数字的中位数


127

如果您有十亿个数字和一百台计算机,那么找到这些数字的中位数的最佳方法是什么?

我有一个解决方案是:

  • 在计算机之间平均分配集合。
  • 对它们进行排序。
  • 找到每组的中位数。
  • 对中位数进行排序。
  • 一次从最低到最高中值合并两套。

如果我们m1 < m2 < m3 ...先合并Set1Set2然后在结果集中,我们可以丢弃所有低于Set12(合并)中位数的数字。因此,在任何时间点我们都有相等大小的集合。顺便说一下,这不能以并行方式完成。有任何想法吗?


3
@John Boker:实际上,这个问题包括两个子问题:1)对列表进行排序; 2)获取索引为5'000'000'000的元素。我几乎不相信数字是有序的。
罗马2010年

3
@罗马:问题不必由您描述的两个子问题组成,例如quickselect。但是quickselect不能并行化,至少不是平凡的。当然,如果数字预先排序,这是一个毫无意义的问题,这是正确的。
史蒂夫·杰索普

5
@fmsf:我不认为任何说英语的国家都会将数十亿英语用于任何官方目的。例如,在英国,我们在1974年就停止使用它。我认为“十亿”一词的意思是一百万,用英语来说是一个错误的把戏问题,而不是“真正的十亿”。当然,在法语中这将是完全不同的事情,但是问题不在法语中。
史蒂夫·杰索普

5
您无需排序!en.wikipedia.org/wiki/...
glebm

2
10亿个数字只是几GB的数据,您不需要多台PC或复杂的算法即可解决此任务。不要太复杂。
user626528

Answers:


54

啊,我的大脑刚刚开始运转,我现在有一个明智的建议。如果这是一次采访,可能为时已晚,但是请不要介意:

机器1将被称为“控制机器”,并且为了论证起见,它要么以所有数据开始,然后以相等的包裹数将其发送给其他99台机器,否则数据开始在机器之间平均分配,将其数据的1/99发送给其他每个人。分区不必相等,只要接近即可。

每一台其他机器都对其数据进行排序,并且这样做的方式有利于首先找到较低的值。因此,例如快速排序,请始终首先对分区的下部进行排序[*]。它会尽快将其数据以递增顺序写回到控制机器(使用异步IO以便继续排序,并且可能启用了Nagle:稍作试验)。

控制机器在数据到达时对其执行99次合并,但丢弃合并的数据,仅保留已看到的值的数量。它计算中位数为1/2十亿分之一和1/2十亿分之一的平均值。

这遭受“最慢的”问题。只有分选机发送了每个小于中位数的值,该算法才能完成。这样的值在其数据范围内很有可能会很高。因此,一旦完成数据的初始分区,估计的运行时间就是将数据的1/99排序并将其发送回控制计算机所需的时间与控件读取数据的1/2时间的总和。 。“组合”介于最大值和那些时间的总和之间,可能接近最大值。

我的直觉是,要想通过网络发送数据要比对数据进行排序(更不用说只选择中位数)更快,它就必须是一个非常快的网络。如果可以假定网络是瞬时的,则可能会有更好的前景,例如,如果您有100个内核,并且具有对包含数据的RAM的平等访问权限。

由于网络I / O可能会受到限制,因此至少可以将数据传回控制机,因此可能会有一些技巧。例如,不是发送“ 1,2,3,.. 100”,而是排序机可以发送一条消息,意思是“ 100个值小于101”。然后,控制机可以执行修改后的合并,在合并机中它找到所有这些最大范围值中的最小值,然后告诉所有分拣机它是什么,以便它们可以(a)告诉控制机如何许多值要“低于”该值,并且(b)从该点继续发送其排序的数据。

更普遍地讲,可能有一个聪明的挑战响应猜谜游戏,控制机可以与99台分拣机一起玩。

但是,这涉及到机器之间的往返,而我较简单的第一个版本避免了这种往返。我真的不知道该如何盲目地评估它们的相对性能,并且由于折衷是复杂的,所以我认为有一个比我想到的要好得多的解决方案,假设这是一个真正的问题。

[*]可用堆栈允许-如果您没有O(N)多余的空间,则您选择先做哪个部分的限制。但是,如果您确实有足够的额外空间,则可以选择;如果您没有足够的空间,则至少可以使用必须要做的一些事情,只需为前几个分区做一小部分。


如果我错了,请纠正我,为什么要对数据执行99向合并,因为它到达后只会在以后丢弃。而是足以保证到达时对数字进行计数?
sreeprasad 2014年

4
@SREEPRASADGOVINDANKUTTY:重复步骤是丢弃所有99个候选者中的最小值,并增加计数。如果没有此99向合并步骤,仅保留所有传入值的计数根本没有用。如果不对它们的输入进行比较,就不知道要丢弃的值低于中位数。
史蒂夫·杰索普

但是,这些分区中的任何一个分区都仅包含高于中位数的数字的可能性很小,因此,它返回的任何较低分区都将高于中位数,但是由于控件不知道,它将以低于分区的方式将其丢弃。中位数并失败...?
Gullydwarf,2015年

@Gullydwarf:多路合并仅丢弃其手中拥有的99个值中的最小值,每个值是其他计算机之一中剩余的最小值。如果其中一个分区完全大于中位数,那么直到中位数超过(这时我们已经完成)之后,它才会成为这99个值中的最小值。因此它不会被丢弃。
史蒂夫·杰索普

52
sort -g numbers | head -n 500000001 | tail -n 2 | dc -e "1 k ? ? + 2 / p"

2
大声笑。这真的有效吗,或者OOM杀手会在完成之前对其进行指责?(在任何合理的计算机上)
Isak Savo 2010年

5
应该做。sort知道如何进行核心外排序,因此不会耗尽内存。
DrPizza 2010年

6
@Zagfai我认为时间不会太长;十亿个数字对于32位整数/浮点数只有4 GB,对于64位整数/双精度数只有8GB。似乎都没有太大的负担。
DrPizza

13
刚刚在Intel i5-4200M @ 3.1 GHz(4核)上尝试过。根据time应用于整个管道的命令,它花费了real=36m24s(“挂钟时间”),user=113m15s (“并行时间”,添加了所有内核)。最长的命令是sort,即使它以100%线程到达我的四个内核,也比其他命令长得多。RAM的消耗是可以接受的。
Morgan Touverey Quilling

11
然后在100台计算机上运行,​​因此可以确保结果正确100倍以上:)
dos

26

我不想在这里成为逆势主义者,但我不认为需要排序,而且我认为任何涉及对十亿个/ 100的数字进行排序的算法都将很慢。让我们考虑一台计算机上的一种算法。

1)从十亿中随机选择1000个值,并使用它们来了解数字的分布,尤其是范围。

2)无需对值进行排序,而是根据您刚刚计算的分布将它们分配给存储桶。选择了存储桶的数量,以便计算机可以有效地处理它们,但否则应尽可能方便。值区范围应确保每个值区中输入的值数量大致相等(这对算法而言并不重要,但可以提高效率。100,000个值的值区可能是合适的)。注意每个存储桶中的值数。这是一个O(n)过程。

3)找出中位数在哪个范围内。这可以通过简单地检查每个存储桶中的总数来完成。

4)通过检查该存储桶中的值来找到实际的中位数。如果您愿意,可以在此处使用排序方式,因为您只排序了10,000个数字。如果该存储桶中的值数量很大,那么您可以再次使用此算法,直到可以排序的数量足够少为止。

这种方法通过在计算机之间划分值来平凡地并行化。每台计算机将每个存储桶中的总数报告给执行第3步的“控制”计算机。对于第4步,每台计算机将相关存储桶中的(排序的)值发送给控制计算机(您也可以并行执行这两种算法,但可能不值得)。

如果第3步和第4步都是微不足道的,则总过程为O(n),前提是铲斗的数量足够大。


1
我认为这介于中位数和快速选择算法之间。 en.wikipedia.org/wiki/Selection_algorithm
Dimath

在第4步中,存储桶可能不会仅包含10,000个。分布可能偏向中间,例如,其中可能包含80%的数据,而这仍然是巨大的。
justhalf

编辑考虑到这一点。
DJClayworth

我喜欢这种方法。
Al Kepp

4
该算法的性能不是O(n):您可能使大多数数字都落在“中位数”存储桶中,并且其性能可能与对所有内容进行排序一样差。
Sklivvz

12

对于现代计算机而言,十亿实际上是一个无聊的任务。我们在这里谈论的是4 GB的4字节整数... 4 GB ...那是某些智能手机的RAM。

public class Median {
    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        int[] numbers = new int[1_000_000_000];

        System.out.println("created array after " +  (System.currentTimeMillis() - start) + " ms");

        Random rand = new Random();
        for (int i = 0; i < numbers.length; i++) {
            numbers[i] = rand.nextInt();
        }

        System.out.println("initialized array after " + (System.currentTimeMillis() - start) + " ms");

        Arrays.sort(numbers);

        System.out.println("sorted array after " + (System.currentTimeMillis() - start) + " ms");

        if (numbers.length % 2 == 1) {
            System.out.println("median = " + numbers[numbers.length / 2 - 1]);
        } else {
            int m1 = numbers[numbers.length / 2 - 1];
            int m2 = numbers[numbers.length / 2];
            double m = ((long) m1 + m2) / 2.0;
            System.out.println("median = " + new DecimalFormat("#.#").format(m));
        }
}

我的机器上的输出:

created array after 518 ms
initialized array after 10177 ms
sorted array after 102936 ms
median = 19196

因此,使用单个内核在不到两分钟的时间(不到1:43,其中0:10是生成随机数)的情况下,就可以在我的计算机上完成此操作,甚至可以进行完整的排序。真的没有幻想。

对于较大的数字集,这无疑是一项有趣的任务。我只想在这里指出一点:十亿是花生。因此,在开始将复杂的解决方案投入到非常简单的任务之前,请三思而后行;)


这就是我在这里的回答中所说的:-) stackoverflow.com/a/31819222/363437
vidstige

1
@vidstige老实说我没看过,但是你是对的。我的回答当然是更多动手了,人们似乎会更加欣赏;)
sfussenegger 2015年

但是,这不是中位数,中位数是(numbers[numbers.length / 2]+numbers[numbers.length / 2+1])/2if numbers.length是偶数,numbers[numbers.length / 2]只有if numbers.length是奇数。
Sklivvz 2015年

@Sklivvz是正确的,但不应显着影响计算中位数所需的时间。
vidstige,2015年

1
@Sklivvz您当然是对的。我刚刚更新了中位数计算。它不会改变其余答案。
sfussenegger 2015年

10

可以使用t-digestQ-digest这样的算法有效地分配阶数统计量(如中位数和第99个百分位数)的估计值。

使用这两种算法,每个节点都会生成一个摘要,该摘要表示本地存储的值的分布。摘要在单个节点处收集,合并(有效地对分布求和),然后可以查找中位数或任何其他百分位数。

这种方法由Elasticsearch以及大概是BigQuery使用(通过QUANTILES函数的描述进行)。


5

这组数字的中位数

2,3,5,7,11,11,67,71,73,79,83,89,97

是67。

这组数字的中位数

2,3,5,7,11,11,67,71,73,79,83,89

是40。

假设问题是大约1,000,000,000个整数(x),其中0> = x <= 2,147,483,647,并且OP正在寻找(element(499,999,999)+ element(500,000,000))/ 2(如果数字已排序)。 还假设所有100台计算机都相等。

使用我的笔记本电脑和GigE ...

我发现我的笔记本电脑可以在1.3秒内排序10,000,000个Int32。因此,粗略估计将是十亿个数字排序将花费100 x 1.3秒(2分钟10秒);)。

千兆位以太网上40MB文件的单向文件传输估计为0.32秒。这意味着来自所有计算机的排序结果将在大约32秒内返回(计算机99在启动后30秒才得到他的文件)。从那里开始,舍弃最低的499,999,998号码,再加上下一个2并除以2,应该花很长时间。


3
下选民评论?这将帮助我了解如何做得更好。
dbasnett 2011年

5
我不是拒绝投票的人,但是排序十亿个数字所花的时间不会是排序一千万个数字的100倍,因为排序列表的最坏情况是O(n log n)。当内存不足并且必须开始在磁盘上进行排序时,排序速度也会降低几个数量级。
理查德·普尔

我认为您走在正确的道路上;如果目标是最快的答案,那么在多台计算机上排序可能是个好主意。但是,如果目标是最短的平均时间,则每台机器自行执行搜索就更有意义了。
查理

假设它们具有相同的因子(可能不是由于内存问题而导致的),然后a*(1e7)log(1e7) = 1.3sec=> a = 1.6e-9sec => a*(1e9)log(1e9) ~ 167sec,所以您的估计并没有那么高。
bcorso

您的估计过于粗略。首先,某些排序算法在最坏的情况下(例如,常用的快速排序)为o(n ^ 2)。其次,您选择了一个与L2缓存大小差不多的测试数据集。这会使结果产生偏差。第三,您(与许多其他答复者一样)假设“数字”的意思是“整数”。它可能表示浮点,双精度或十进制,它们具有非常不同的性能特征。
Sklivvz

5

这可能会让人们感到惊讶,但是如果数字是足够小的整数以适合32位(或更小)的整数-只需执行存储桶排序即可!对于任意数量的32位int并以O(n)运行,仅需要16GB的ram,在合理的n(例如十亿)下,它应优于任何分布式系统。

有了排序列表后,选择中位数就很简单了。实际上,您不需要构造排序列表,而只需查看存储桶即可。

一个简单的实现如下所示。仅适用于16位整数,但扩展到32位应该很容易。

#include <stdio.h>
#include <string.h>

int main()
{
    unsigned short buckets[65536];
    int input, n=0, count=0, i;

    // calculate buckets
    memset(buckets, 0, sizeof(buckets));
    while (scanf("%d", &input) != EOF)
    {
        buckets[input & 0xffff]++;
        n++;
    }

    // find median 
    while (count <= n/2)
    {
        count += buckets[i++];
    }

    printf("median: %d\n", i-1);

    return 0;
}

使用具有十亿(10 9)个数字的文本文件并time像这样运行

time ./median < billion

在我的机器上产生的运行时间为1m49.293s。大多数运行时间可能也是磁盘IO。


这并不能真正回答问题,它取决于假设。例如,您甚至都不知道它们是整数。
Sklivvz

它不会以什么方式回答问题?是的,我的答案假设数字是整数。我试图清楚地陈述我的假设。
vidstige

您似乎没有说明整数是一个假设,也没有解决如何使用OP询问的100台计算机。您可以计算一个节点上的中位数,但这不是“最佳”解决方案,除非您说明原因。另外,如果数字位数不同,基数排序也不是o(n),根据en.wikipedia.org/wiki/Radix_sort#Efficiency的说法,在这种情况下,基数排序肯定是o(n log n)
Sklivvz

我首先说“如果整数足够小以适合一个32位整数 ”,对于恒定的字长w,基数排序为O(n),这在您发布的链接中非常清楚地描述。在这里,我假设的32恒定的字大小
vidstige

1
您对其他99台计算机的处理与此答案无关。您可以将它们堆叠在一起以形成金字塔或燃烧它们。或者只是忽略它们。
vidstige,2015年

3

奇怪的是,我认为如果您有足够的计算机,则比使用O(n)中位数查找算法更好。(不过,除非您的核心速度非常非常慢,否则我只使用一个,并且O(n)仅对1e9个数字使用中值查找算法;但是,如果您使用1e12,那可能就不太实用了。)

无论如何,让我们假设我们有多个log n内核来处理此问题,并且我们不关心功耗,只是快速获得答案。让我们进一步假设这是一台SMP计算机,其中所有数据都已加载到内存中。(例如,Sun的32核计算机就是这种类型。)

一个线程将列表盲切成相等大小的片段,并告诉其他M个线程对其进行排序。这些线程会(n/M) log (n/M)及时地这样做。然后,他们不仅返回其中位数,而且还返回其25%和75%百分数(如果选择稍有不同的数字,则最坏的情况会更好)。现在您拥有4M的数据范围。然后,您可以对这些范围进行排序,并在列表中向上移动,直到找到一个数字,这样,如果丢弃所有小于或包含该数字的范围,则将丢弃一半的数据。那是您的中位数的下限。对上限执行相同的操作。这需要一些M log M时间,所有内核都必须等待,所以这真的很浪费M^2 log M潜在的时间。现在,您有一个单线程告诉其他线程将所有数据扔出该范围之外的数据(每次通过时应该扔掉大约一半的数据),然后重复一次-这是一个快速的操作,因为已经对数据进行了排序。您不必重复log(n/M)多次,而是可以更快地获取剩余数据并O(n)在其上使用标准中值查找器。

因此,总复杂度类似于O((n/M) log (n/M) + M^2 log M log (n/M))。因此,O(n)如果M >> log(n/M)M^3 log M < n,这比对一个核进行中值排序要快,这对于您描述的场景是正确的。

考虑到效率低下,我认为这是一个非常糟糕的主意,但是它更快。


o(n / M log(n / M))实际上是o(n log n),因为o(n / M log(n / M))= 1 / M o(n(log n-log M) )= o(n log n)。您不能真正将其与o(n)进行比较,因为“ o”基本上意味着“对于具有非常大的n且具有未指定常数的变量,它与n成正比”。除非您知道这些常数,否则您无法进行比较,但是对于足够大的N,这些常数不是主要的。对于较低的数字,所有下注均关闭,o(1)很容易比o(n!)慢。
Sklivvz 2015年

@Sklivvz- nM是可以任意缩放的变量,因此其中一个都包含。特别是,我假设M> log n,这意味着,如果您关心它n log n而不是just n,那么您也必须关心它M
Rex Kerr

3

这可以比投票的算法(n log n)更快地完成

-订单统计分布式选择算法-O(n)可以
将问题简化为在未排序的数组中找到第k个数的原始问题。
-计算排序直方图O(n)
您必须假设一些关于数字范围的属性-该范围是否适合内存?-外部合并排序-O(n log n)-如上所述
您基本上在第一遍对数字进行排序,然后在第二遍求中值。
-如果知道数字的分布,则可以生成其他算法。

有关更多详细信息和实现,请参见:http :
//www.fusu.us/2013/07/median-in-large-set-across-1000-servers.html


2

一台计算机足以解决问题。

但是,假设有100台计算机。您应该做的唯一复杂的事情就是对列表进行排序。将其拆分为100个部分,将一个部分发送到每台计算机,然后在其中分类,然后合并部分。

然后从排序列表的中间获取数字(即索引为5 000 000 000)。


3
无论如何,我的代表现在很圆润:)
Roman

合并最多为O(n),您可以在O(n)的单个核中找到中值,因此这似乎会产生很多额外的工作而没有收益。
Rex Kerr

2

这取决于您的数据。最坏的情况是它是均匀分布的数字。

在这种情况下,您可以找到O(N)时间的中位数,如以下示例所示:

假设您的数字是2,7,5,10,1,6,4,4,6,10,4,7,1,8,4,9,9,3,4,3(范围是1-10) 。

我们创建3个存储桶:1-3、4-7、8-10。请注意,顶部和底部的大小相等。

我们用数字填充水桶,计算每个水桶跌落的次数,最大值和最小值

  • 低(5):2,1,1,3,3,最小1,最大3
  • 中(10):7,5,6,4,4,6,4,7,4,4,最小值4,最大值7
  • 高(5):10、10、8、9、9,最小8,最大10

均值落在中间部分,其余部分则忽略

我们创建3个存储桶:4、5-6、7。低将以5开始计数,最大值为3,高将以最小8开始计数5。

对于每个数字,我们计算落在高低桶中的有多少,最大值和最小值,并保留中间桶。

  • 旧低(5)
  • 低(5):4、4、4、4、4,最大4
  • 中(3):5,6,6
  • 高(2):7、7分7
  • 老高(5)

现在我们可以直接计算中位数:这样的情况

old low    low          middle  high  old high
x x x x x  4 4 4 4 4 4   5 6 6  7 7   x x x x x

因此中位数为4.5。

假设您对分布有所了解,则可以微调如何定义范围以优化速度。在任何情况下,性能都应为O(N),因为1 + 1/3 + 1/9 ... = 1.5

由于边缘情况,您需要最小值和最大值(例如,中位数是旧低和下一个元素的最大值之间的平均值)。

所有这些操作都可以并行化,您可以将1/100的数据提供给每台计算机,并计算每个节点中的3个存储桶,然后分配您保留的存储桶。这又使您有效地使用网络,因为每个数字平均传递1.5次(因此O(N))。您甚至可以打败如果仅在节点之间传递最小数字(例如,如果节点1具有100个数字,节点2具有150个数字,那么节点2可以将25个数字提供给节点1)。

除非您对分布有更多了解,否则我怀疑您在这里会比O(N)做得更好,因为您实际上需要至少计数一次元素。


1
当所有数字都相等时,不是真的(对于您的算法而言)更糟糕的情况吗?如果我是正确的话,那么您的所有存储桶都不会与中间的存储桶充满所有元素。因此,您每次都必须遍历所有元素,以指数级的速度快速到达间隔的中间。我相信O(n log n)在那种情况下会是这样。是否有意义 ?顺便说一句,我喜欢你的想法
Dici

1
@Dici不是真的:首先,您可以轻松快捷地“全部相同”,因为您知道最小和最大。就像我在回答中所说的那样,知道分布可以驱动您的选择。其次,仍然需要o(n)+o(n/3)+o(n/9)+...静止o(n)和不静止o(n log n)
Sklivvz 2015年

另一方面,可能存在不同的最坏情况,即U形分布。我需要考虑一下,确定最坏的情况,但是o(n)如果使用天真分区,它可能会比在这种情况下更糟。
Sklivvz

是的,最小值和最大值将非常容易处理“相同”的情况
Dici

2

一种更简单的方法是拥有加权数。

  • 在计算机之间拆分大集合
  • 对每组排序
  • 遍历小集合,并计算重复元素的权重
  • 将每2套合并为1套(每套已经排序)更新权重
  • 保持合并,直到只有一套
  • 遍历此集合的权重,直到达到OneBillion / 2

1

将10 ^ 9数字,10 ^ 7分配给每台计算机,每台计算机约80MB。每台计算机都会对其编号进行排序。然后计算机1将自己的数字与计算机2,计算机3和4等的数字进行合并排序...然后计算机1将数字的一半写回2、3至4等。然后1合并对计算机的数字进行排序1,2,3,4,将其写回。等等。根据计算机上RAM的大小,您可能会在每一步都没有将所有数字写回单独的计算机上而逃脱,也许可以将计算机1上的数字累加几步,但是您可以进行数学计算。

哦,终于得到了500000000th和500000001st值的平均值(但是请检查那里是否有足够的00,我还没有)。

编辑:@罗马-好吧,即使您不相信它,这也是真的,那么我揭示这个命题的真伪是没有意义的。我的意思是说,有时候在比赛中蛮力有时会比较聪明。我花了大约15秒钟的时间设计出了一种算法,我有信心自己可以实现该算法,该算法可以工作,并且可以适应各种输入和计算机数量的大小,并且可以根据计算机和计算机的特性进行调整。网络安排。如果您或其他任何人花了15分钟的时间来设计一个更复杂的算法,我就有14分45秒的优势来编写解决方案并开始运行。

但是我自由地承认这全是断言,我什么也没衡量。


在这里,我们只是对所有数字进行合并排序。我们可以使用以下更好的方法吗?-“我们可以在登录时间找到两个排序列表的中位数。n是每个列表的长度。”
anony 2010年

1
@anony-当您回答自己的问题时,我将对我的解决方案进行编码,测试和完成。我希望有更好的方法,但是有时并行化一个简单的方法可以让我自由地解决真正困难的问题。
高性能Mark'3

你真的在7分钟内完成了吗?即使是真的,我也无法相信。我完成了类似的任务(这是一次大学任务),花了大约2个小时来实现和测试所有远程处理的东西(我使用了Java RMI)。
罗马2010年

我明白您在说什么,但基于同样的理由,DrPizza拥有一个甚至更快的解决方案,该解决方案是将所有数据排序在一个节点上,而忽略其他99个节点。我们都不知道数据有多昂贵应该考虑转移资金,因此我们所有人都只是在选择听起来似乎合理的折衷方案。您的解决方案会多次传输所有数据,因此我对此有些怀疑,但这肯定是一个解决方案。
史蒂夫·杰索普

'似乎很合理'-对我@Steve来说已经足够了!尤其是在回答一个难以置信的难以置信的问题时。
Performance Performance Mark

1

可以在节点上使用未按以下方式在节点之间排序的数据(例如从日志文件中排序)来完成此操作。

有1个父节点和99个子节点。子节点有两个api调用:

  • stats():返回最小值,最大值和计数
  • compare(median_guess):返回计数匹配值,小于计数值,大于计数值

父节点在所有子节点上调用stats(),注意所有节点的最小值和最大值。

现在可以通过以下方式进行二进制搜索:

  1. 将最小和最大舍入取整-这是中位数“猜测”
  2. 如果大于计数大于小于计数,则将最小值设置为猜测值
  3. 如果大于计数小于小于计数,则将最大值设置为猜测值
  4. 如果计数为奇数,则最小值和最大值相等时
  5. 如果最大数<=最小数+ guess.match_count时计数甚至结束,则可以按以下方式在使用未排序数据(例如来自日志文件)的节点上完成。

有1个父节点和99个子节点。子节点有两个api调用:

  • stats():返回最小值,最大值和计数
  • compare(median_guess):返回计数匹配值,小于计数值,大于计数值

父节点在所有子节点上调用stats(),注意所有节点的最小值和最大值。

现在可以通过以下方式进行二进制搜索:

  1. 将最小和最大舍入取整-这是中位数“猜测”
  2. 如果大于计数大于小于计数,则将最小值设置为猜测值
  3. 如果大于计数小于小于计数,则将最大值设置为猜测值
  4. 如果计数为奇数,则最小值和最大值相等时
  5. 如果最大数<=最小数+猜测数时计数甚至结束。

如果可以使用O(N / Mlogn / M)排序对stats()和compare()进行预先计算,则对于计算。然后,您可以在固定时间内执行compare(),因此整个操作(包括预计算)将以O(N / MlogN / M)+ O(logN)运行

让我知道我是否犯了错!


是的,我只会做二进制搜索。只需要多次调用每台计算机即可节省网络带宽。而且,每台机器都可以有一个“枢轴”,在该枢轴的适当位置交换枢轴两侧的数字以节省时间。(枢轴是先前对中位数的估计,因此下一次,只需要遍历枢轴一侧的所有数字即可)
罗伯特·金

0

怎么样:-每个节点可以接受10亿个100的数字。在每个节点上,可以对元素进行排序并找到中位数。查找中位数的中位数。通过汇总所有节点上小于中位数的数字的计数,我们可以找出中位数中位数所占的x%:y%划分。现在要求所有节点删除小于中位数的元素(以30%:70%分割为例)。删除30%的数字。10亿的70%是7亿。现在,所有删除少于300万个节点的节点都可以将这些多余的节点发送回主计算机。主计算机以这样的方式进行重新分配,即现在所有节点将具有几乎相等数量的节点(700万个)。现在问题已经减少到7亿个……继续进行下去,直到我们得到一个可以在一个comp上计算出的更小的集合。


从本质上讲,我们总是将问题集至少减少30%,并由此实现许多并行计算。每个节点以一千万开头,每次迭代将其数据集减少30%。
anony 2010年

在第一次迭代中,我们寻找500M的数字。在第二次迭代中-如果删除的数字数量为3亿,那么我们寻找2亿个数字,依此类推...
anony

2
这看起来似乎是在正确的轨道上,但是您并没有非常清楚地说明如何避免在30%/ 70%的比例下意外丢掉中位数。以下面的反例为例:假设您的前29%都是全零,所有其他块的总和为1000,并且每组块比最后一个多一个。第30个百分位数的中位数将丢弃所有29%的数据,而将近丢弃61%的数据的一半,即29 + 30%= 59%的数据。糟糕,我们只是抛出了真实的中位数!因此,显然您不是这个意思,或者至少您的意思是比我解释的更聪明。
Rex Kerr

0

让我们首先弄清楚如何在一台机器上找到n个数字的中位数:我基本上是在使用分区策略。

问题:selection(n,n / 2):从最小数字中找到第n / 2个数字。

您选择说中间元素k并将数据划分为2个子数组。第一个包含所有元素<k,第二个包含所有元素> = k。

如果sizeof(1st子数组)> = n / 2,则说明该子数组包含中位数。然后,您可以抛出第二个子数组。解决此问题选择(sizeof 1st sub-array,n / 2)

在其他情况下,抛出该第一个子数组并求解选择(第二个子数组,n / 2-sizeof(第一个子数组))

递归执行。

时间复杂度为 O(n)预期时间。

现在,如果我们有很多机器,则在每次迭代中,我们都必须处理要拆分的数组,然后将数组分配到diff机器中。每个机器处理它们的数组块并将摘要发送回集线器控制机器,即第一子数组的大小和第二子数组的大小。集线器计算机将汇总汇总,并确定要进一步处理的子数组(第一或第二个)和选择的第二个参数,并将其发送回每台计算机。等等。

使用map reduce可以很好地实现此算法吗?

看起来如何?


0

我认为史蒂夫·杰索普(Steve Jessop)的答案将是最快的。

如果网络数据传输大小是瓶颈,这是另一种方法。

Divide the numbers into 100 computers (10 MB each). 
Loop until we have one element in each list     
    Find the meadian in each of them with quickselect which is O(N) and we are processing in parallel. The lists will be partitioned at the end wrt median.
    Send the medians to a central computer and find the median of medians. Then send the median back to each computer. 
    For each computer, if the overall median that we just computed is smaller than its median, continue in the lower part of the list (it is already partitioned), and if larger in the upper part.
When we have one number in each list, send them to the central computer and find and return the median.

每个32 MB,您的意思是?
Dici

您继续在列表的下部表示什么?
Ruthvik Vaila

0

我会这样:

在开始时,所有100个工作都要找出最高和最低的数字;每台计算机都有其查询的数据库/文件部分;

当找到最高和最低编号时,一台计算机读取数据,并将每个编号平均分配给其余的99个;这些数字以相等的间隔分布;(一个可能会花费-1亿至0,另一个可能会花费-0至1亿,依此类推);

在接收数字的同时,99台计算机中的每台已经对它们进行排序;

然后,很容易找到中位数...查看每台计算机有多少个数字,将它们全部相加(有多少个数字之和,而不是数字本身),除以2;然后除以2。计算数字在哪台计算机上以及在哪个索引上;

:)香草

PS似乎这里有很多混乱;中位数-是数字排序列表中间的数字!



0

如果数字不是唯一的,并且仅属于某个范围,即它们是重复的,那么我想到的一个简单解决方案是,将数字平均分配到99台计算机中,并保持一台计算机为主。现在,每台计算机都会迭代给定的数字,并将每个数字的计数存储在哈希集中。每次在分配给该特定计算机的数字集中重复该数字时,它都会更新其哈希集中的计数。

然后,所有计算机将其哈希集返回给主计算机。主机组合哈希集,对哈希集中找到的相同密钥的计数求和。例如,机器#1的哈希集的条目为(“ 1”,7),而机器#2的哈希集的条目为(“ 1”,9),因此主计算机在组合哈希集时将条目为()。 (“ 1”,16),依此类推。

哈希集合并后,只需对键进行排序,现在就可以从排序后的哈希集中轻松找到第(n / 2)个项目和第(n + 2/2)个项目。

如果十亿个数字不同,则此方法将无益。


0

好吧,假设您知道不同整数的数量(例如)为40亿,那么您可以将它们存储到64k个存储桶中,并从集群中的每台计算机(100台计算机)中获得每个存储桶的分布式计数。结合所有这些计数。现在,找到具有中位数的存储桶,这一次只需要为目标存储桶中的64k元素请求存储桶。这需要对您的“集群”进行O(1)(特别是2)查询。:D


0

我的一分钱值得,毕竟这已经被别人提出来了:

在单台机器上查找中位数为O(N):https : //en.wikipedia.org/wiki/Selection_algorithm

向100台计算机发送N个号码也是O(N)。因此,为了使使用100台计算机变得有趣,或者通信必须相对较快,或者N太大,以至于在N / 100可行的情况下,一台计算机无法处理它,或者我们只想考虑数学问题而不用担心数据通讯。

为了简明扼要,我假设在合理的范围内,我们可以发送/分发数字而不影响效率分析。

然后考虑以下方法,其中将一台机器指定为某些常规处理的“主”。这将相对较快,因此“主机”还参与了每台计算机执行的常见任务。

  1. 每台机器都接收N / 100个数字,计算自己的中位数,然后将该信息发送给主机。
  2. 主机编译所有不同中位数的排序列表,然后将其发送回每台机器,以定义存储桶的有序序列(在每台机器上相同),每个中位数(一个单值存储桶)一个,在两个中间值之间的每个间隔一个相邻的中位数。当然,也有低端和高端存储桶,它们的值低于最低中位数,高于最高值。
  3. 每台计算机计算每个存储桶中有多少个数字,并将该信息传达回主服务器。
  4. 主机确定哪个存储桶包含中位数,有多少较低的值(总计)低于该存储桶以及有多少较低的值。
  5. 如果选择的存储桶是单值存储桶(中位数之一),否则选择的存储桶仅包含1(N个奇数)或2(N个偶数)值。否则,我们将通过以下(明显的)修改重复上述步骤:
  6. 仅将所选存储区中的编号从主服务器(重新)分配到100台计算机,而且
  7. 我们不打算(在每台机器上)计算中位数,而是计算第k个值,其中我们考虑了从总数中丢弃了多少个较高的数字以及多少个较低的数字。从概念上讲,每台计算机还具有其丢弃的低/高数字的份额,并且在计算集合中(概念上)包括(丢弃的)数字的新中位数时要考虑到这一点。

时间复杂度:

  1. 稍加思考,就会使您相信,在每个步骤上,要分析的值的总数至少减少了两倍(2个情况会很糟糕;您可能希望得到更好的降低)。由此我们得到:
  2. 假设找到中位数(或第k个值)为O(N),需要花费c * N的时间,此时因数c不会随N的变化太大,因此我们可以将其作为一个常数。最多2 * c * N / 100次即可获得最终结果。因此,使用100台机器可使我们的加速因子至少为100/2。
  3. 就像最初提到的那样:在机器之间传递数字所花费的时间可能使得仅在一台机器上简单地执行所有操作变得更具吸引力。但是,如果我们采用分布式方法,则在所有步骤中一起通信的总数不超过2 * N(第一次为N,第二次为<= N / 2,<=一半第三,依此类推)。

-1
  1. 将十亿个数字划分为100台计算机。每台机器将有10 ^ 7个数字。

  2. 对于机器的每个传入号码,请将其存储在频率图中,即“号码”->“计数”。还要在每台机器上存储最小值。

  3. 查找每台计算机的中位数:从每台计算机的最小值开始,对计数求和,直到达到中位数索引。每台机器的中位数约为 小于和大于5 * 10 ^ 6的数字。

  4. 查找所有中位数的中位数,该中位数将小于或大于约。50 * 10 ^ 7个数字,是10亿个数字的中位数。

现在对第二步进行一些优化:将计数存储在可变位数组中,而不是存储在频率图中。例如:假设从一台机器的最小编号开始,这些是频率计数:

[min number] - 8 count
[min+1 number] - 7 count
[min+2 number] - 5 count

上面可以存储在位数组中为:

[min number] - 10000000
[min+1 number] - 1000000
[min+2 number] - 10000

请注意,由于每台机器仅处理10 ^ 7个数字,因此每台机器总共花费约10 ^ 7位。10 ^ 7位= 1.25 * 10 ^ 6字节,即1.25MB

因此,使用上述方法,每台计算机将需要1.25MB的空间来计算本地中位数。中位数的中位数可以从这100个局部中位数计算得出,结果中位数为10亿。


如果数字是浮点数怎么办?
Sklivvz

-1

我建议一种近似计算中位数的方法。:)如果这十亿个数字是随机排列的,我想我可以随机选择十亿个数字的1/100或1/10,用100台机器对其进行排序,然后选择它们的中位数。或将十亿个数字分成100个部分,让每台机器随机选择每个部分的1/10,计算它们的中位数。之后,我们有100个数字,我们可以更轻松地计算100个数字的中位数。只是一个建议,我不确定这在数学上是否正确。但是我认为您可以将结果显示给一位不太出色的经理。


显然这是不正确的,我强烈建议您不要以为面试官是可以欺骗的
笨猪

哈哈,好的,尽管它不会改变您的答案不正确的事实。这很容易证明
Dici

好吧,在阅读了一些有关统计的讲座之后,我认为这个想法随机地从十亿个数字的1/100甚至1/1000中选取并计算出它们的中位数并不算太坏。这只是一个近似的计算。
lazyboy 2015年

-3

史蒂夫·杰索普(Steve Jessop)的答案是错误的:

考虑以下四组:

{2,4,6,8,10}

{21,21,24,26,28}

{12,14,30,32,34}

{16,18,36,38,40}

中位数为21,包含在第二组中。

这四组的中位数是6、24、30、36,总中位数是27。

因此,在第一个循环之后,这四个组将变为:

{6,8,10}

{24,26,28}

{12,14,30}

{16,18,36}

21号已经被错误丢弃。

该算法仅支持有两个组的情况。

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.