В общем, это сложно сделать правильно, особенно для обработки вырожденных рядов, которые уже отсортированы или имеют несколько значений в начале списка, но конец списка имеет значения в другом диапазоне.
Основная идея создания гистограммы наиболее перспективна. Это позволяет вам накапливать информацию о распределении и отвечать на запросы (например, медиана) из нее. Медиана будет приблизительной, так как вы явно не сохраняете все значения. Место для хранения фиксировано, поэтому оно будет работать с любой последовательностью длины.
Но вы не можете просто построить гистограмму, скажем, из первых 100 значений и постоянно использовать эту гистограмму ... изменяющиеся данные могут сделать эту гистограмму недействительной. Таким образом, вам нужна динамическая гистограмма, которая может изменять диапазон и ячейки на лету.
Создайте структуру, которая имеет N бункеров. Вы будете хранить значение X каждого перехода слота (всего N + 1 значений), а также заполнение ячейки.
Поток в ваших данных. Запишите первые значения N + 1. Если поток заканчивается до этого, прекрасно, у вас есть все загруженные значения, и вы можете найти точную медиану и вернуть ее. Еще используйте значения, чтобы определить вашу первую гистограмму. Просто отсортируйте значения и используйте их в качестве определений бинов, каждый из которых имеет заполнение 1. Это нормально, если у вас есть дуплексы (0 бинов ширины).
Теперь поток новых значений. Для каждого бинарный поиск, чтобы найти корзину, к которой он принадлежит.
В общем случае вы просто увеличиваете количество пользователей этого бина и продолжаете.
Если ваш образец выходит за границы гистограммы (самый высокий или самый низкий), просто увеличьте диапазон конечного бина, чтобы включить его.
Когда ваш поток завершен, вы можете найти медианное значение выборки, найдя ячейку с равным населением по обе стороны от нее и линейно интерполировав оставшуюся ширину ячейки.
Но этого недостаточно ... вам все еще нужно АДАПТИРОВАТЬ гистограмму к данным, когда они поступают в поток. Когда корзина переполняется, вы теряете информацию о перераспределении этой корзины.
Вы можете исправить это, адаптировавшись на основе некоторой эвристики ... Самый простой и надежный из них - это если бин достигает некоторого определенного порогового значения (что-то вроде 10 * v / N, где v = количество значений, замеченных до сих пор в потоке, N - количество корзин), вы РАЗДЕЛИТЕ эту переполненную корзину. Добавьте новое значение в средней точке контейнера, дайте каждой стороне половину от исходного количества элементов корзины. Но теперь у вас слишком много корзин, поэтому вам нужно УДАЛИТЬ корзину. Хорошая эвристика для этого - найти корзину с наименьшим произведением населения и ширины. Удалите его и объедините с левым или правым соседом (какой из соседей сам имеет наименьшее произведение ширины и численности населения). Готово!
Обратите внимание, что слияние или разделение корзин приводит к потере информации, но это неизбежно. У вас есть только фиксированное хранилище.
Этот алгоритм хорош тем, что работает с всеми типами входных потоков и дает хорошие результаты. Если у вас есть возможность выбора порядка выборки, лучше выбрать случайную выборку, поскольку она сводит к минимуму разбиения и слияния.
Алгоритм также позволяет запрашивать любой процентиль, а не только медиану, поскольку у вас есть полная оценка распределения.
Я использую этот метод в своем собственном коде во многих местах, в основном для отладки журналов ... где некоторые статистические данные, которые вы записываете, имеют неизвестное распространение. С этим алгоритмом вам не нужно угадывать заранее.
Недостатком является неравная ширина бина, что означает, что вы должны выполнять бинарный поиск для каждой выборки, поэтому ваш сетевой алгоритм имеет значение O (NlogN).