Найти текущую медиану из потока целых чисел

Возможный дубликат:
Передвижной медианный алгоритм в C

Учитывая, что целые числа считываются из потока данных. Найти медианную часть элементов, прочитанных до сих пор эффективным способом.

Решение, которое я прочитал: мы можем использовать максимальную кучу на левой стороне, чтобы представлять элементы, которые меньше эффективной медианной, и минимальную кучу с правой стороны, чтобы представлять элементы, которые больше эффективной медианной.

После обработки входящего элемента количество элементов в кучах отличается не более чем на 1 элемент. Когда обе кучи содержат одинаковое количество элементов, мы находим среднее значение данных корня кучи как эффективную медианную. Когда кучи не сбалансированы, мы выбираем эффективную медиану из корня кучи, содержащей больше элементов.

Но как бы мы построили максимальную кучу и минимальную кучу, то есть как мы узнаем эффективную медиану здесь? Я думаю, что мы вставим 1 элемент в max-heap, а затем следующий 1 элемент в min-heap и т.д. Для всех элементов. Исправьте меня, если я ошибаюсь.

Ответ 1

Существует множество различных решений для поиска медианы из потоковых данных, я кратко расскажу о них в самом конце ответа.

Вопрос о деталях конкретного решения (максимальное кучное/кучное решение) и о том, как работает решение на основе кучи:

Для первых двух элементов добавьте меньшее значение в maxHeap слева, а большее - к minHeap справа. Затем обрабатывайте данные потока потока один за другим,

Step 1: Add next item to one of the heaps

   if next item is smaller than maxHeap root add it to maxHeap,
   else add it to minHeap

Step 2: Balance the heaps (after this step heaps will be either balanced or
   one of them will contain 1 more item)

   if number of elements in one of the heaps is greater than the other by
   more than 1, remove the root element from the one containing more elements and
   add to the other one

Затем в любой момент времени вы можете вычислить медиану следующим образом:

   If the heaps contain equal amount of elements;
     median = (root of maxHeap + root of minHeap)/2
   Else
     median = root of the heap with more elements

Теперь я расскажу о проблеме в целом, как обещал в начале ответа. Поиск работы медианы из потока данных является сложной проблемой, и найти эффективное точное решение с ограничениями памяти, вероятно, невозможно для общего случая. С другой стороны, если данные имеют некоторые характеристики, которые мы можем использовать, мы можем разработать эффективные специализированные решения. Например, если мы знаем, что данные являются интегральными типами, то мы можем использовать подсчет сортировки, который может дать вам постоянный алгоритм постоянного времени памяти, Решение на основе кучи является более общим решением, поскольку оно может использоваться и для других типов данных (удваивается). И, наконец, если точная медиана не требуется и достаточно аппроксимации, вы можете просто попытаться оценить функцию плотности вероятности для данных и оценить медиану, используя это.

Ответ 2

Если вы не можете одновременно держать все предметы в памяти, эта проблема становится намного сложнее. Решение кучи требует, чтобы вы одновременно удерживали все элементы в памяти. Это невозможно в большинстве реальных приложений этой проблемы.

Вместо этого, поскольку вы видите числа, отслеживайте счетчик количества раз, когда вы видите каждое целое число. Предполагая 4 байтовых целых числа, 2 ^ 32 ведра или не более 2 ^ 33 целых чисел (ключ и счетчик для каждого int), который составляет 2 ^ 35 байт или 32 ГБ. Вероятно, это будет намного меньше, потому что вам не нужно хранить ключ или считать для тех записей, которые равны 0 (например, как defaultdict в python). Это занимает постоянное время, чтобы вставить каждое новое целое число.

Затем в любой точке, чтобы найти медиану, просто используйте подсчеты, чтобы определить, какое целое является средним. Это занимает постоянное время (хотя и большая константа, но, тем не менее, постоянная).

Ответ 3

Если дисперсия входа статистически распределена (например, нормальная, log-normal... и т.д.), то выборка коллектора является разумным способом оценки процентилей/медианов из произвольно длинного потока чисел.

int n = 0;  // Running count of elements observed so far  
#define SIZE 10000
int reservoir[SIZE];  

while(streamHasData())
{
  int x = readNumberFromStream();

  if (n < SIZE)
  {
       reservoir[n++] = x;
  }         
  else 
  {
      int p = random(++n); // Choose a random number 0 >= p < n
      if (p < SIZE)
      {
           reservoir[p] = x;
      }
  }
}

"резервуар" - это бегущий, однородный (справедливый) образец всех входных данных - независимо от размера. Поиск медианного (или любого процентиля) - это прямолинейный вопрос сортировки резервуара и опроса интересного пункта.

Поскольку резервуар является фиксированным размером, сортировка может считаться эффективной O (1) - и этот метод работает как с постоянным временем, так и с потреблением памяти.

Ответ 4

Эта проблема имеет точное решение, которое требует только n самых последних замеченных элементов, которые должны храниться в памяти. Он быстро и хорошо масштабируется.

An indexable skiplist поддерживает O (ln n) вставку, удаление и индексированный поиск произвольных элементов при сохранении упорядоченного порядка. В сочетании с очередью FIFO, которая отслеживает первую старую запись, решение прост:

class RunningMedian:
    'Fast running median with O(lg n) updates where n is the window size'

    def __init__(self, n, iterable):
        self.it = iter(iterable)
        self.queue = deque(islice(self.it, n))
        self.skiplist = IndexableSkiplist(n)
        for elem in self.queue:
            self.skiplist.insert(elem)

    def __iter__(self):
        queue = self.queue
        skiplist = self.skiplist
        midpoint = len(queue) // 2
        yield skiplist[midpoint]
        for newelem in self.it:
            oldelem = queue.popleft()
            skiplist.remove(oldelem)
            queue.append(newelem)
            skiplist.insert(newelem)
            yield skiplist[midpoint]

Вот ссылки на полный рабочий код (простая в понимании версия класса и оптимизированная версия генератора с индексируемым кодеком skiplist inlined):

Ответ 5

Самый эффективный способ вычисления процентиля найденного потока - это алгоритм P²: Raj Jain, Imrich Chlamtac: Алгоритм P² для динамического расчета квантилей и гистограмм без сохранения наблюдений. Commun. ACM 28 (10): 1076-1085 (1985)

Алгоритм прямо внедрен и работает очень хорошо. Однако это оценка, поэтому помните об этом. Из реферата:

Предложен эвристический алгоритм для динамического вычисления qf медианного и других квантилей. Оценки производятся динамически по мере создания наблюдений. Наблюдения не сохраняются; поэтому алгоритм имеет очень малое и фиксированное требование хранения независимо от количества наблюдений. Это делает его идеальным для реализации в квантильной микросхеме, которая может использоваться в промышленных контроллерах и рекордерах. Алгоритм далее расширяется до графика гистограммы. Точность алгоритма анализируется.

Ответ 6

Интуитивно понятный способ думать об этом заключается в том, что если бы у вас было полностью сбалансированное бинарное дерево поиска, тогда корнем был бы медианный элемент, поскольку там было бы одинаковое количество меньших и больших элементов. Теперь, если дерево не заполнено, это будет не совсем так, поскольку на последнем уровне будут отсутствовать элементы.

Поэтому вместо этого мы можем получить медиану и два сбалансированных бинарных дерева, одно для элементов, меньших, чем медиана, и одно для элементов, превышающих медиану. Два дерева должны быть одинакового размера.

Когда мы получаем новое целое число из потока данных, мы сравниваем его с медианой. Если оно больше медианы, мы добавляем его к нужному дереву. Если два размера дерева отличаются более чем на 1, мы удаляем элемент min правого дерева, делаем его новой медианой и помещаем старую медиану в левое дерево. Аналогично для меньших.

Ответ 7

Эффективное - это слово, которое зависит от контекста. Решение этой проблемы зависит от количества запросов, выполненных относительно количества вставок. Предположим, вы вставляете N чисел и K раз к концу, вас интересовала медиана. Сложность алгоритма на основе кучи была бы O (N log N + K).

Рассмотрим следующий вариант. Залейте числа в массиве и для каждого запроса запустите алгоритм линейного выбора (скажем, используя опорную точку быстрого сортировки). Теперь у вас есть алгоритм с временем работы O (K N).

Теперь, если K достаточно мало (нечетные запросы), последний алгоритм фактически эффективнее и наоборот.

Ответ 8

Разве вы не можете сделать это с помощью одной кучи? Обновление: нет. См. Комментарий.

Инвариантно: после чтения входов 2*n минимальная куча содержит n наибольшую из них.

Loop: Прочитайте 2 ввода. Добавьте их в кучу и удалите кучу мин. Это восстанавливает инвариант.

Поэтому, когда считываются входы 2n, куча min является n-м по величине. Для усреднения двух элементов вокруг медианного положения потребуется небольшое дополнительное усложнение и обработка запросов после нечетного количества входов.