2014-01-10 3 views
10

я пытался узнать о распределенных вычислений и наткнулся на проблему нахождения медианы большого набора чисел:Найти медиану N^2 чисел, имеющих память для N из них

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

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

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

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

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

+0

Это можно сделать, используя две кучи. См. Самый популярный ответ на этот вопрос [http://stackoverflow.com/questions/3440905/find-the-median-from-a-stream-of-integers/3441042#3441042] – deinst

+0

@ deinst- Этот подход требует некоторые нетривиальные модификации, если вы хотите поместить все в основную память. – templatetypedef

+0

Существуют линейные алгоритмы времени для поиска медианы. Вы упомянули распределенные вычисления. Вы предполагаете, что у вас есть K процессоров с N элементами памяти каждый, и вы хотите получить хорошее распределенное время работы, например. где вы делите на K? Для этого вам в идеале нужен линейный временной базовый алгоритм, а не алгоритм O (N log N). – user2566092

ответ

5

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

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

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

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

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

+0

Но будет ли это не нужно знать ряд чисел? Я согласен с гистограммой, я значительно уменьшу размер проблемы. Но что, если диапазон не может быть известен? – Akshya11235

+0

Я обновил свой ответ. Диапазон может быть максимальным значением, которое могут удерживать ваши переменные, или вы можете вычислить макс. Или, может быть, ваши ценности ограничены. – jbgs

+0

@jbgs Не могли бы вы рассказать о том, как работает функция getindex? – Agostino

1

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

2

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

 Смежные вопросы

  • Нет связанных вопросов^_^