[algorithm] 10 억 숫자의 중앙값 계산

10 억 개의 컴퓨터와 100 대의 컴퓨터가 있다면이 숫자의 중앙값을 찾는 가장 좋은 방법은 무엇입니까?

내가 가진 한 가지 해결책은 다음과 같습니다.

  • 컴퓨터간에 세트를 동일하게 분할하십시오.
  • 그것들을 정렬하십시오.
  • 각 세트의 중앙값을 찾으십시오.
  • 중앙값 세트를 정렬하십시오.
  • 가장 낮은 중앙값에서 가장 높은 중앙값까지 한 번에 두 세트를 병합하십시오.

우리가있는 경우 m1 < m2 < m3 ...먼저 병합을 Set1하고 Set2그 결과 세트에서 우리는 모든 숫자의 평균보다 낮은 삭제할 수 있습니다 Set12(통합). 따라서 어느 시점에서나 동일한 크기의 세트가 있습니다. 그런데 이것은 병렬 방식으로 수행 할 수 없습니다. 어떤 아이디어?



답변

아, 내 뇌는 이제 막 시작 됐습니다. 저는 현명한 제안을했습니다. 인터뷰를 한 경우 아마도 너무 늦었지만 신경 쓰지 마십시오.

기계 1은 “제어 기계”라고하며, 논쟁의 여지가 있기 때문에 모든 데이터로 시작하여 같은 소포로 다른 99 대의 기계로 보내거나 데이터가 기계간에 균등하게 분배되기 시작합니다. 데이터의 1/99를 서로에게 보냅니다. 파티션이 같을 필요는 없으며 닫기 만하면됩니다.

서로 다른 컴퓨터는 데이터를 정렬하며 더 낮은 값을 먼저 찾는 것을 선호합니다. 예를 들어, 빠른 정렬은 항상 파티션의 아래쪽을 먼저 정렬합니다 [*]. 데이터는 가능한 한 빨리 순서대로 제어 시스템에 다시 기록합니다 (정렬을 계속하기 위해 비동기 IO 사용, 아마도 Nagle on : 비트 테스트).

제어 시스템은 도착한 데이터에 대해 99-way 병합을 수행하지만, 표시된 값의 수만 유지하면서 병합 된 데이터를 버립니다. 중앙값을 1/2 십억 및 1/2 십억에 1을 더한 평균으로 계산합니다.

이것은 “무리가 가장 느린”문제로 어려움을 겪고 있습니다. 알고리즘은 중간 값보다 작은 모든 값이 정렬 기계에 의해 전송 될 때까지 완료 될 수 없습니다. 그러한 가치 중 하나가 데이터 소포 내에서 상당히 높을 가능성은 합리적입니다. 따라서 데이터의 초기 파티셔닝이 완료되면 예상 실행 시간은 데이터의 1/99를 정렬하여 제어 컴퓨터로 다시 보내는 시간과 컨트롤이 데이터의 1/2을 읽는 시간의 조합입니다. . “조합”은 최대 시간과 그 시간의 합계 사이에있을 수 있으며 아마도 최대에 가깝습니다.

내 본능은 네트워크를 통해 데이터를 전송하는 것보다 데이터를 정렬하는 것보다 빠르기 때문에 (중앙값을 선택하는 것만 제외하고) 상당히 빠른 네트워크 여야한다는 것입니다. 예를 들어 데이터가 포함 된 RAM에 동등한 액세스 권한을 가진 100 개의 코어가있는 경우 네트워크가 즉각적인 것으로 추정 될 수있는 경우 더 나은 전망이 될 수 있습니다.

네트워크 I / O가 한계가 있기 때문에 최소한 데이터가 제어 시스템으로 되돌아 오는 경우 약간의 트릭이있을 수 있습니다. 예를 들어, “1,2,3, .. 100″을 보내는 대신 정렬 시스템에서 “100보다 작은 100 개 값”을 의미하는 메시지를 보낼 수 있습니다. 그런 다음 제어 시스템은 수정 된 병합을 수행 할 수 있습니다. 여기서 병합 된 값 중 가장 작은 값 중 가장 작은 값을 찾은 다음 모든 정렬 시스템에 해당 값을 알려주므로 (a) 제어 시스템에 많은 값이 해당 값 아래로 “계산”되고 (b) 해당 지점에서 정렬 된 데이터 전송을 재개합니다.

보다 일반적으로, 컨트롤 머신이 99 개의 정렬 머신으로 플레이 할 수있는 영리한 도전-응답 추측 게임이있을 것입니다.

그러나 이것은 기계 사이의 왕복 여행과 관련이 있습니다. 단순한 첫 번째 버전은 피합니다. 나는 그들의 상대적인 성과를 맹목적으로 추정하는 방법을 정말로 모른다. 그리고 절충은 복잡하기 때문에, 이것이 실제 문제라고 가정하면, 내가 생각할 것보다 훨씬 더 나은 해결책이 있다고 생각한다.

[*] 사용 가능한 스택 허용-O (N) 추가 공간이없는 경우 먼저 수행 할 부분의 선택이 제한됩니다. 그러나 여분의 공간이 충분하면 선택을 할 수 있고 공간이 충분하지 않으면 처음 몇 개의 파티션에 대해 작은 부분을 먼저 수행하여 모서리를 자르는 데 필요한 것을 사용할 수 있습니다.


답변

sort -g numbers | head -n 500000001 | tail -n 2 | dc -e "1 k ? ? + 2 / p"


답변

나는 여기에 반대되는 것을 싫어하지만 정렬이 필요하다고 생각하지 않으며 10 억 / 100 개의 숫자 정렬과 관련된 알고리즘이 느릴 것이라고 생각합니다. 한 컴퓨터의 알고리즘을 생각해 봅시다.

1) 10 억에서 무작위로 1000 개의 값을 선택하고이를 사용하여 숫자, 특히 범위의 분포에 대한 아이디어를 얻습니다.

2) 값을 정렬하는 대신 방금 계산 한 분포를 기준으로 버킷에 값을 할당하십시오. 버킷 수는 컴퓨터가 효율적으로 처리 할 수 ​​있도록 선택되지만 그렇지 않으면 편리해야합니다. 버킷 범위는 대략 동일한 수의 값이 각 버킷에 들어가도록해야합니다 (알고리즘에는 중요하지 않지만 효율성에 도움이됩니다. 10 만 버킷이 적절할 수 있음). 각 버킷의 값 수를 기록하십시오. 이것은 O (n) 프로세스입니다.

3) 중앙값이 어느 버킷 범위인지 확인하십시오. 각 버킷의 총 수를 간단히 검사하면됩니다.

4) 해당 버킷의 값을 검사하여 실제 중앙값을 찾으십시오. 10,000 개의 숫자 만 정렬하기 때문에 원하는 경우 여기에서 정렬을 사용할 수 있습니다. 해당 버킷의 값 수가 크면 정렬 할만큼 작은 수가 될 때까지이 알고리즘을 다시 사용할 수 있습니다.

이 접근 방식은 컴퓨터간에 값을 나누어 사소하게 병렬화됩니다. 각 컴퓨터는 각 버킷의 총계를 3 단계를 수행하는 ‘제어’컴퓨터에보고합니다. 4 단계의 경우 각 컴퓨터는 관련 버킷의 (정렬 된) 값을 제어 컴퓨터에 보냅니다 (두 알고리즘 모두 병렬로 수행 할 수 있음) 그러나 가치가 없을 것입니다).

버킷 수가 충분히 많으면 3 단계와 4 단계가 모두 간단하므로 전체 프로세스는 O (n)입니다.


답변

실제로 10 억은 현대 컴퓨터에서 지루한 작업입니다. 우리는 여기서 4GB 정수의 4 바이트 정수에 대해 이야기하고 있습니다 … 4GB … 그것은 일부 스마트 폰의 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

따라서 이것은 단일 코어를 사용하여 2 분 이내에 (1:43은 임의의 숫자를 생성하는) 내 컴퓨터에서 완료되며 전체 정렬을 수행합니다. 정말 멋진 것은 없습니다.

이것은 분명히 더 큰 숫자 집합에 대한 흥미로운 작업입니다. 저는 여기서 지적하고자합니다. 10 억은 땅콩입니다. 놀랍도록 간단한 작업에서 복잡한 솔루션을 던지기 전에 두 번 생각하십시오.)


답변

중간 값 및 99 번째 백분위 수와 같은 차수 통계 의 추정t-digest 또는 Q-digest 와 같은 알고리즘으로 효율적으로 배포 될 수 있습니다 .

각 알고리즘을 사용하여 각 노드는 다이제스트를 생성하여 로컬에 저장된 값의 분포를 나타냅니다. 다이제스트는 단일 노드에서 수집되어 병합 (분포를 효과적으로 합산) 한 다음 중앙값 또는 다른 백분위 수를 찾을 수 있습니다.

이 접근법은 elasticsearch 및 아마도 BigQuery (QUANTILES 함수의 설명으로 이동)에서 사용됩니다.


답변

이 숫자 집합의 중앙값

2, 3, 5, 7, 11, 13, 67, 71, 73, 79, 83, 89, 97

67입니다.

이 숫자 집합의 중앙값

2, 3, 5, 7, 11, 13, 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를 정렬 할 수 있다는 것입니다. 따라서 대략적인 수치는 10 억 개의 숫자 정렬에 100 x 1.3 초 (2 분 10 초)가 소요될 것입니다.

기가비트 이더넷에서 40MB 파일의 단방향 파일 전송 예상치는 .32 초입니다. 이는 모든 컴퓨터에서 정렬 된 결과가 약 32 초 내에 반환됨을 의미합니다 (컴퓨터 99는 시작 후 30 초까지 파일을 얻지 못했습니다). 거기에서 가장 낮은 499,999,998 개의 숫자를 버리고 다음 2를 더하고 2로 나누는 데 시간이 오래 걸리지 않습니다.


답변

이것은 사람들을 놀라게 할 수 있지만 숫자가 32 비트 (또는 더 작은) 안에 들어갈 정도로 작은 정수라면 버킷 정렬을하십시오! 32 비트 int에 제한없이 16GB의 램만 필요하며 O (n)에서 실행되며, 이는 분산 시스템보다 성능이 우수해야합니다 (예 : 10 억).

정렬 된 목록이 있으면 중간 값을 선택하는 것이 쉽지 않습니다. 실제로 정렬 된 목록을 구성 할 필요는 없지만 버킷을 보는 것만으로도 목록을 작성해야합니다.

간단한 구현은 아래와 같습니다. 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 억 (10 9 ) 숫자 의 텍스트 파일을 사용하여 다음 과 time같이 실행

time ./median < billion

내 컴퓨터에서 1m49.293s의 실행 시간을 얻습니다. 대부분의 실행 시간은 아마도 디스크 IO 일 것입니다.