如果您有十亿个数字和一百台计算机,那么找到这些数字的中位数的最佳方法是什么?
我有一个解决方案是:
- 在计算机之间平均分配集合。
- 对它们进行排序。
- 找到每组的中位数。
- 对中位数进行排序。
- 一次从最低到最高中值合并两套。
如果我们m1 < m2 < m3 ...
先合并Set1
,Set2
然后在结果集中,我们可以丢弃所有低于Set12
(合并)中位数的数字。因此,在任何时间点我们都有相等大小的集合。顺便说一下,这不能以并行方式完成。有任何想法吗?
如果您有十亿个数字和一百台计算机,那么找到这些数字的中位数的最佳方法是什么?
我有一个解决方案是:
如果我们m1 < m2 < m3 ...
先合并Set1
,Set2
然后在结果集中,我们可以丢弃所有低于Set12
(合并)中位数的数字。因此,在任何时间点我们都有相等大小的集合。顺便说一下,这不能以并行方式完成。有任何想法吗?
Answers:
啊,我的大脑刚刚开始运转,我现在有一个明智的建议。如果这是一次采访,可能为时已晚,但是请不要介意:
机器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)多余的空间,则您选择先做哪个部分的限制。但是,如果您确实有足够的额外空间,则可以选择;如果您没有足够的空间,则至少可以使用必须要做的一些事情,只需为前几个分区做一小部分。
sort -g numbers | head -n 500000001 | tail -n 2 | dc -e "1 k ? ? + 2 / p"
time
应用于整个管道的命令,它花费了real=36m24s
(“挂钟时间”),user=113m15s
(“并行时间”,添加了所有内核)。最长的命令是sort
,即使它以100%线程到达我的四个内核,也比其他命令长得多。RAM的消耗是可以接受的。
我不想在这里成为逆势主义者,但我不认为需要排序,而且我认为任何涉及对十亿个/ 100的数字进行排序的算法都将很慢。让我们考虑一台计算机上的一种算法。
1)从十亿中随机选择1000个值,并使用它们来了解数字的分布,尤其是范围。
2)无需对值进行排序,而是根据您刚刚计算的分布将它们分配给存储桶。选择了存储桶的数量,以便计算机可以有效地处理它们,但否则应尽可能方便。值区范围应确保每个值区中输入的值数量大致相等(这对算法而言并不重要,但可以提高效率。100,000个值的值区可能是合适的)。注意每个存储桶中的值数。这是一个O(n)过程。
3)找出中位数在哪个范围内。这可以通过简单地检查每个存储桶中的总数来完成。
4)通过检查该存储桶中的值来找到实际的中位数。如果您愿意,可以在此处使用排序方式,因为您只排序了10,000个数字。如果该存储桶中的值数量很大,那么您可以再次使用此算法,直到可以排序的数量足够少为止。
这种方法通过在计算机之间划分值来平凡地并行化。每台计算机将每个存储桶中的总数报告给执行第3步的“控制”计算机。对于第4步,每台计算机将相关存储桶中的(排序的)值发送给控制计算机(您也可以并行执行这两种算法,但可能不值得)。
如果第3步和第4步都是微不足道的,则总过程为O(n),前提是铲斗的数量足够大。
对于现代计算机而言,十亿实际上是一个无聊的任务。我们在这里谈论的是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是生成随机数)的情况下,就可以在我的计算机上完成此操作,甚至可以进行完整的排序。真的没有幻想。
对于较大的数字集,这无疑是一项有趣的任务。我只想在这里指出一点:十亿是花生。因此,在开始将复杂的解决方案投入到非常简单的任务之前,请三思而后行;)
(numbers[numbers.length / 2]+numbers[numbers.length / 2+1])/2
if numbers.length
是偶数,numbers[numbers.length / 2]
只有if numbers.length
是奇数。
可以使用t-digest或Q-digest这样的算法有效地分配阶数统计量(如中位数和第99个百分位数)的估计值。
使用这两种算法,每个节点都会生成一个摘要,该摘要表示本地存储的值的分布。摘要在单个节点处收集,合并(有效地对分布求和),然后可以查找中位数或任何其他百分位数。
这种方法由Elasticsearch以及大概是BigQuery使用(通过QUANTILES函数的描述进行)。
这组数字的中位数
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,应该花很长时间。
a*(1e7)log(1e7) = 1.3sec
=> a = 1.6e-9sec
=> a*(1e9)log(1e9) ~ 167sec
,所以您的估计并没有那么高。
这可能会让人们感到惊讶,但是如果数字是足够小的整数以适合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。
奇怪的是,我认为如果您有足够的计算机,则比使用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
,这比对一个核进行中值排序要快,这对于您描述的场景是正确的。
考虑到效率低下,我认为这是一个非常糟糕的主意,但是它更快。
n
和M
是可以任意缩放的变量,因此其中一个都包含。特别是,我假设M
> log n
,这意味着,如果您关心它n log n
而不是just n
,那么您也必须关心它M
。
这可以比投票的算法(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
这取决于您的数据。最坏的情况是它是均匀分布的数字。
在这种情况下,您可以找到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。请注意,顶部和底部的大小相等。
我们用数字填充水桶,计算每个水桶跌落的次数,最大值和最小值
均值落在中间部分,其余部分则忽略
我们创建3个存储桶:4、5-6、7。低将以5开始计数,最大值为3,高将以最小8开始计数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)做得更好,因为您实际上需要至少计数一次元素。
O(n log n)
在那种情况下会是这样。是否有意义 ?顺便说一句,我喜欢你的想法
o(n)+o(n/3)+o(n/9)+...
静止o(n)
和不静止o(n log n)
。
o(n)
如果使用天真分区,它可能会比在这种情况下更糟。
一种更简单的方法是拥有加权数。
将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秒的优势来编写解决方案并开始运行。
但是我自由地承认这全是断言,我什么也没衡量。
可以在节点上使用未按以下方式在节点之间排序的数据(例如从日志文件中排序)来完成此操作。
有1个父节点和99个子节点。子节点有两个api调用:
父节点在所有子节点上调用stats(),注意所有节点的最小值和最大值。
现在可以通过以下方式进行二进制搜索:
有1个父节点和99个子节点。子节点有两个api调用:
父节点在所有子节点上调用stats(),注意所有节点的最小值和最大值。
现在可以通过以下方式进行二进制搜索:
如果可以使用O(N / Mlogn / M)排序对stats()和compare()进行预先计算,则对于计算。然后,您可以在固定时间内执行compare(),因此整个操作(包括预计算)将以O(N / MlogN / M)+ O(logN)运行
让我知道我是否犯了错!
怎么样:-每个节点可以接受10亿个100的数字。在每个节点上,可以对元素进行排序并找到中位数。查找中位数的中位数。通过汇总所有节点上小于中位数的数字的计数,我们可以找出中位数中位数所占的x%:y%划分。现在要求所有节点删除小于中位数的元素(以30%:70%分割为例)。删除30%的数字。10亿的70%是7亿。现在,所有删除少于300万个节点的节点都可以将这些多余的节点发送回主计算机。主计算机以这样的方式进行重新分配,即现在所有节点将具有几乎相等数量的节点(700万个)。现在问题已经减少到7亿个……继续进行下去,直到我们得到一个可以在一个comp上计算出的更小的集合。
让我们首先弄清楚如何在一台机器上找到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可以很好地实现此算法吗?
看起来如何?
我认为史蒂夫·杰索普(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.
我会这样:
在开始时,所有100个工作都要找出最高和最低的数字;每台计算机都有其查询的数据库/文件部分;
当找到最高和最低编号时,一台计算机读取数据,并将每个编号平均分配给其余的99个;这些数字以相等的间隔分布;(一个可能会花费-1亿至0,另一个可能会花费-0至1亿,依此类推);
在接收数字的同时,99台计算机中的每台已经对它们进行排序;
然后,很容易找到中位数...查看每台计算机有多少个数字,将它们全部相加(有多少个数字之和,而不是数字本身),除以2;然后除以2。计算数字在哪台计算机上以及在哪个索引上;
:)香草
PS似乎这里有很多混乱;中位数-是数字排序列表中间的数字!
您可以使用锦标赛树方法来找到中位数。我们可以创建具有1000个离开节点的树,这样每个叶节点都是一个数组。然后我们在不同的数组之间进行n / 2个锦标赛.n / 2个锦标赛之后的根值就是结果。
http://www.geeksforgeeks.org/tournament-tree-and-binary-heap/
如果数字不是唯一的,并且仅属于某个范围,即它们是重复的,那么我想到的一个简单解决方案是,将数字平均分配到99台计算机中,并保持一台计算机为主。现在,每台计算机都会迭代给定的数字,并将每个数字的计数存储在哈希集中。每次在分配给该特定计算机的数字集中重复该数字时,它都会更新其哈希集中的计数。
然后,所有计算机将其哈希集返回给主计算机。主机组合哈希集,对哈希集中找到的相同密钥的计数求和。例如,机器#1的哈希集的条目为(“ 1”,7),而机器#2的哈希集的条目为(“ 1”,9),因此主计算机在组合哈希集时将条目为()。 (“ 1”,16),依此类推。
哈希集合并后,只需对键进行排序,现在就可以从排序后的哈希集中轻松找到第(n / 2)个项目和第(n + 2/2)个项目。
如果十亿个数字不同,则此方法将无益。
我的一分钱值得,毕竟这已经被别人提出来了:
在单台机器上查找中位数为O(N):https : //en.wikipedia.org/wiki/Selection_algorithm。
向100台计算机发送N个号码也是O(N)。因此,为了使使用100台计算机变得有趣,或者通信必须相对较快,或者N太大,以至于在N / 100可行的情况下,一台计算机无法处理它,或者我们只想考虑数学问题而不用担心数据通讯。
为了简明扼要,我假设在合理的范围内,我们可以发送/分发数字而不影响效率分析。
然后考虑以下方法,其中将一台机器指定为某些常规处理的“主”。这将相对较快,因此“主机”还参与了每台计算机执行的常见任务。
时间复杂度:
将十亿个数字划分为100台计算机。每台机器将有10 ^ 7个数字。
对于机器的每个传入号码,请将其存储在频率图中,即“号码”->“计数”。还要在每台机器上存储最小值。
查找每台计算机的中位数:从每台计算机的最小值开始,对计数求和,直到达到中位数索引。每台机器的中位数约为 小于和大于5 * 10 ^ 6的数字。
查找所有中位数的中位数,该中位数将小于或大于约。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亿。
我建议一种近似计算中位数的方法。:)如果这十亿个数字是随机排列的,我想我可以随机选择十亿个数字的1/100或1/10,用100台机器对其进行排序,然后选择它们的中位数。或将十亿个数字分成100个部分,让每台机器随机选择每个部分的1/10,计算它们的中位数。之后,我们有100个数字,我们可以更轻松地计算100个数字的中位数。只是一个建议,我不确定这在数学上是否正确。但是我认为您可以将结果显示给一位不太出色的经理。