Найдите медиану N ^ 2 чисел, имеющих память для N из них

Я пытался узнать о распределенных вычислениях и столкнулся с проблемой поиска медианы большого набора чисел:

Предположим, что мы имеем большой набор чисел (допустим, количество элементов N * K), которые не могут вписаться в память (размер N). Как мы находим медиану этих данных? Предположим, что операции, выполняемые в памяти, независимы, то есть мы можем считать, что есть K машин, каждый из которых может обрабатывать не более N элементов.

Я думал, что медиана медиан может использоваться для этой цели. Мы можем одновременно загружать N чисел в память. Мы находим медиану этого набора в O(logN) времени и сохраняем его.

Затем мы сохраняем все эти медианы K и выясняем медиану медианов. Снова O(logK), до сих пор сложность была O(K*logN + logK).

Но эта медиана медианов - всего лишь приблизительная медиана. Я думаю, что будет оптимально использовать его в качестве стержня, чтобы получить наилучшую производительность, но для этого нам нужно будет вместить все номера N * K в памяти.

Как мы можем найти реальную медиану набора теперь, когда у нас есть хороший приблизительный стержень?

Ответ 1

Почему вы не строите гистограмму? То есть количество случаев (значений), которые попадают в каждую из нескольких категорий. Категории должны быть последовательными, неперекрывающимися интервалами переменной.

С помощью этой гистограммы вы можете сделать первую оценку медианы (то есть медиана находится между [a, b]) и узнать, сколько значений попадает в этот интервал (H). Если H <= N, снова прочитайте числа, игнорируя их за пределами этого интервала и переместив в ОЗУ числа в интервале. Найдите медианную.

Если H > N, выполните новый раздел интервала и повторите процедуру. Это не должно занимать более 2 или 3 итераций.

Обратите внимание, что для каждого раздела вам нужно только сохранить a, b, Delta и массив с количеством значений, которые попадают в каждый интервал.

ИЗМЕНИТЬ. Это было немного сложнее, чем я ожидал. На каждой итерации после оценки интервала, в который попадает медиана, мы должны также рассмотреть "насколько" гистограмма, которую мы оставляем справа и слева от этого интервала. Я тоже изменил условие остановки. Во всяком случае, я сделал реализацию на С++.

#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 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 элементов и вычислять гистограмму. Как только он будет рассчитан, они отправят его на мастер-машину, которая суммирует все гистограммы (легко, это может быть очень мало... алгоритм работает даже с гистограммами из двух интервалов). Затем он будет отправлять новые инструкции (т.е. Новый интервал) на подчиненные машины для вычисления новых гистограмм. Обратите внимание: каждая машина не нуждается в знаниях о N элементах, которые принадлежат другим машинам.

Ответ 2

Возьмем случайную выборку из N из них. При постоянной вероятности, зависящей от c, медиана этой случайной выборки находится в c * N местах медианы. Если вы делаете это дважды, то с постоянной вероятностью вы сузили возможные положения медианы до линейно многих. Сделайте что-нибудь ужасное, что вам нравится, чтобы выбрать элемент соответствующего ранга.

Ответ 3

Если вы считаете, что ваши номера являются двоичными целыми числами B бит (плавающая точка тоже прекрасна, потому что вы можете сортировать на основе знака, а затем основываться на экспоненте, а затем на основе мантиссы), то вы можете решить проблему в O(N^2 B / K) time, если у вас есть процессоры K и N^2. Вы в основном выполняете двоичный поиск. Начните с точки опоры, равной середине диапазона, и используйте ваши процессоры K, чтобы подсчитать, сколько чисел меньше и равно и больше, чем опорный стержень. Затем вы узнаете, равна ли медиана оси вращения или больше или меньше точки поворота. Продолжайте бинарный поиск. Каждый шаг двоичного поиска занимает O(N^2 /K) время, чтобы пройти через список чисел, давая O(N^2 B / K) общее время работы.