10

分散コンピューティングについて学ぼうとしていたところ、多数の数値セットの中央値を見つけるという問題に遭遇しました。

メモリ (サイズ N) に収まらない大きな数のセット (要素の数が N*K であるとします) があるとします。このデータの中央値をどのように見つけますか? メモリ上で実行される操作は独立していると仮定します。つまり、それぞれが最大で N 個の要素を処理できる K 台のマシンがあると見なすことができます。

中央値の中央値がこの目的に使用できると思いました。一度に N 個の数値をメモリにロードできます。そのセットの中央値を時間で見つけてO(logN)保存します。

次に、これらの K 個の中央値をすべて保存して、中央値の中央値を見つけます。繰り返しO(logK)ますが、これまでのところ複雑さはありませんO(K*logN + logK)

しかし、この中央値の中央値は、おおよその中央値にすぎません。最適なパフォーマンスを得るには、これをピボットとして使用するのが最適だと思いますが、そのためには、すべての N*K 数値をメモリに収める必要があります。

適切な近似ピボットが得られたので、セットの実際の中央値を見つけるにはどうすればよいでしょうか?

4

3 に答える 3

5

ヒストグラムを作成しませんか?つまり、いくつかのカテゴリのそれぞれに分類されるケース (値) の数です。カテゴリは、変数の連続した重複しない間隔である必要があります。

このヒストグラムを使用して、最初に中央値を推定し (つまり、中央値は [a,b] の間にあります)、この間隔 (H) に含まれる値の数を知ることができます。H<=N の場合、数値を再度読み取り、この間隔外の数値を無視して、間隔内の数値を RAM に移動します。中央値を見つけます。

H>N の場合、間隔の新しい分割を行い、手順を繰り返します。2 回または 3 回以上反復する必要はありません。

パーティションごとに、a、b、デルタ、および各サブインターバルに含まれる値の数を含む配列のみを格納する必要があることに注意してください。

編集。私が予想していたよりも少し複雑であることがわかりました。中央値が入る間隔を推定した後の各反復では、この間隔の右側と左側に「どのくらい」ヒストグラムを残すかについても考慮する必要があります。停止条件も変更しました。とにかく、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 回の繰り返しで十分です。つまり、ネットワークまたは HDD からセットを 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 個の要素についての知識を持っている必要がないことに注意してください。

于 2014-01-10T19:48:41.820 に答える
2

それらの N 個のランダム サンプルを取得します。c に依存する一定の確率で、この無作為標本の中央値は、中央値の c*N 桁内にあります。これを 2 回行うと、一定の確率で、中央値の可能な位置を直線的に多くに絞り込むことができます。適切なランクの要素を選択するために、好きなことを何でもしてください。

于 2014-01-10T19:57:22.090 に答える
1

数値がビット バイナリ整数であると仮定する場合B(符号に基づいて、次に指数に基づいて、次に仮数に基づいて並べ替えることができるため、浮動小数点も問題ありません)、プロセッサと数値O(N^2 B / K)があれば、時間内に問題を解決できます。 . 基本的に二分探索を行います。範囲の中央に等しいピボットから開始し、プロセッサを使用して、ピボットよりも小さい数と等しい数、および大きい数の数をカウントします。次に、中央値がピボットと等しいか、ピボットより大きいか小さいかがわかります。二分探索を続けます。各二分探索ステップでは、数字のリストを調べるのに時間がかかるため、全体の実行時間が長くなります。KN^2KO(N^2 /K)O(N^2 B / K)

于 2014-01-10T19:42:55.757 に答える