【问题标题】:Find a median of N^2 numbers having memory for N of them找到对其中 N 个有记忆的 N^2 个数字的中位数
【发布时间】:2014-01-10 18:47:36
【问题描述】:

我在尝试学习分布式计算时遇到了一个寻找大量数字的中位数的问题:

假设我们有大量无法放入内存(大小为 N)的数字(假设元素的数量为 N*K)。我们如何找到这些数据的中位数?假设在内存上执行的操作是独立的,即我们可以认为有 K 台机器,每台机器最多可以处理 N 个元素。

我认为中位数的中位数可以用于此目的。 我们可以一次将 N 个数字加载到内存中。我们在O(logN) 时间内找到该集合的中位数并保存。

然后我们保存所有这些 K 中位数并找出中位数的中位数。再说一次O(logK),到目前为止,复杂度一直是O(K*logN + logK)

但是这个中位数只是一个近似的中位数。我认为最好将它用作获得最佳案例性能的支点,但为此我们需要将所有 N*K 数字都放入内存中。

既然我们有一个很好的近似枢轴,我们如何才能找到集合的实际中位数?

【问题讨论】:

  • 可以使用两个堆来完成。查看此 [问题][stackoverflow.com/questions/3440905/… 的最受欢迎答案
  • @deinst- 如果您想将所有内容都放入主内存,则该方法需要进行一些重要的修改。
  • 有线性时间算法可以找到中位数。你提到了分布式计算。您是否假设您有 K 个处理器,每个处理器具有 N 个内存元素,并且您希望获得良好的分布式运行时间,例如你在哪里除以K?为此,您最好使用线性时间基本算法,而不是 O(N log N) 算法。
  • 我们知道这些数字吗?例如,它们是唯一的整数吗?它们的范围是多少(最小和最大数量)?

标签: algorithm optimization median median-of-medians


【解决方案1】:

你为什么不建立一个直方图? IE。属于几个类别中的每个类别的案例(值)的数量。类别应该是变量的连续、非重叠区间。

使用此直方图,您可以初步估计中位数(即中位数在 [a,b] 之间),并知道有多少值落入此区间 (H)。如果 H

如果 H>N,则对区间进行新的划分并重复该过程。它不应该超过 2 或 3 次迭代。

请注意,对于每个分区,您只需要存储 a、b、一个 Delta 和包含每个子区间的值数量的数组。

编辑。结果证明它比我预期的要复杂一些。在估计中位数落入的区间后的每次迭代中,我们还应该考虑在该区间的右侧和左侧留下“多少”直方图。我也改变了停止条件。不管怎样,我做了一个 C++ 实现。

#include <iostream>
#include <algorithm>
#include <time.h>
#include <stdlib.h>

//This is N^2... or just the number of values in your array,
//note that we never modify it except at the end (just for sorting
//and testing purposes).
#define N2 1000000
//Number of elements in the histogram. Must be >2
#define HISTN 1000

double findmedian (double *values, double min, double max);
int getindex (int *hist);
void put (int *hist, double min, double max, double val, double delta);


int main ()
{
    //Set max and min to the max/min values your array variables can hold,
    //calculate it, or maybe we know that they are bounded
    double max=1000.0;
    double min=0.0;
    double delta;
    double values[N2];
    int hist[HISTN];
    int ind;
    double median;
    int iter=0;
    //Initialize with random values   
    srand ((unsigned) (time(0)));
    for (int i=0; i<N2; ++i)
        values[i]=((double)rand()/(double)RAND_MAX);

    double imin=min;
    double imax=max;

    clock_t begin=clock(); 
    while (1) {
        iter++;
        for (int i=0; i<HISTN; ++i)
            hist[i]=0;

        delta=(imax-imin)/HISTN;
        for (int j=0; j<N2; ++j)
            put (hist, imin, imax, values[j], delta);

        ind=getindex (hist);
        imax=imin;
        imin=imin+delta*ind;
        imax=imax+delta*(ind+1);

        if (hist[ind]==1 || imax-imin<=DBL_MIN) {
            median=findmedian (values, imin, imax);
            break;
        }   
    }

    clock_t end=clock();
    std::cout << "Median with our algorithm: " << median << " - " << iter << "iterations of the algorithm" << std::endl; 
    double time=(double)(end-begin)/CLOCKS_PER_SEC;
    std::cout << "Time: " << time << std::endl;  

    //Let's compare our result with the median calculated after sorting the
    //array
    //Should be values[(int)N2/2] if N2 is odd
    begin=clock();
    std::sort (values, values+N2);
    std::cout << "Median after sorting: " << values[(int)N2/2-1] << std::endl;
    end=clock();
    time=(double)(end-begin)/CLOCKS_PER_SEC;
    std::cout << "Time: " << time << std::endl;  

    return 0;
}

double findmedian (double *values, double min, double max) {
    for (int i=0; i<N2; ++i) 
        if (values[i]>=min && values[i]<=max)
            return values[i];

    return 0;
}

int getindex (int *hist)
{
    static int pd=0;
    int left=0;
    int right=0; 
    int i;

    for (int k=0; k<HISTN; k++)
        right+=hist[k];

    for (i=0; i<HISTN; i++) {
        right-=hist[i];
        if (i>0)
            left+=hist[i-1];
        if (hist[i]>0) {
            if (pd+right-left<=hist[i]) {
                pd=pd+right-left;
                break;
            }
        }

    }

    return i;
}

void put (int *hist, double min, double max, double val, double delta)
{
    int pos;
    if (val<min || val>max)
        return;

    pos=(val-min)/delta;
    hist[pos]++;
    return;
}

我还包括了一个简单的中位数计算(排序),以便与算法的结果进行比较。 4 或 5 次迭代就足够了。这意味着我们只需要从网络或硬盘读取设备 4-5 次。

一些结果:

N2=10000
HISTN=100

Median with our algorithm: 0.497143 - 4 iterations of the algorithm
Time: 0.000787
Median after sorting: 0.497143
Time: 0.001626

(Algorithm is 2 times faster)

N2=1000000
HISTN=1000

Median with our algorithm: 0.500665 - 4 iterations of the algorithm
Time: 0.028874
Median after sorting: 0.500665
Time: 0.097498

(Algorithm is ~3 times faster)

如果要算法并行化,每台机器可以有N个元素,计算直方图。一旦计算出来,他们会将其发送到主机,这将对所有直方图求和(很容易,它可以非常小......该算法甚至适用于 2 个间隔的直方图)。然后它将向从机发送新指令(即新间隔)以计算新的直方图。请注意,每台机器不需要了解其他机器拥有的 N 个元素。

【讨论】:

  • 但这不需要知道一系列数字吗?我同意直方图,我将大量减少问题的大小。但是如果不知道范围怎么办?
  • 我更新了我的答案。范围可以是变量可以保持的最大值,也可以计算最大值。或者也许你的价值观是有界的。
  • @jbgs 您能否就 getindex 函数的工作原理添加一些评论?
【解决方案2】:

随机抽取其中 N 个样本。在依赖于 c 的恒定概率的情况下,该随机样本的中位数在中位数的 c*N 个位置内。如果你这样做两次,那么,以恒定的概率,你已经将中位数的可能位置缩小到线性多。做任何你喜欢的可怕的事情来选择适当等级的元素。

【讨论】:

    【解决方案3】:

    如果您假设您的数字是B 位二进制整数(浮点也可以,因为您可以根据符号排序,然后根据指数,然后根据尾数),那么您可以在 @ 中解决问题如果您有 K 处理器和 N^2 号码,则为 987654322@ 时间。您基本上进行二分搜索:从等于范围中间的枢轴开始,并使用您的K 处理器计算有多少数字小于、等于和大于枢轴。然后你会知道中位数是等于枢轴还是大于或小于枢轴。继续二分查找。每个二分搜索步骤都需要O(N^2 /K) 时间来遍历数字列表,从而得到O(N^2 B / K) 的总体运行时间。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-11
      • 1970-01-01
      • 2015-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-08
      相关资源
      最近更新 更多