这在一般情况下很难正确处理,特别是要处理已经排序的退化序列,或者列表开头有一堆值,但列表结尾的值在不同的范围内。
制作直方图的基本思路最为可行。这让您可以积累分布信息并从中回答查询(如中位数)。由于您显然没有存储所有值,因此中位数将是近似值。存储空间是固定的,因此它适用于任何长度的序列。
但是,您不能仅从前100个值构建直方图并持续使用该直方图...正在更改的数据可能会使该直方图无效。因此,您需要一个动态直方图,可以根据需要动态更改其范围和区间。
创建一个具有N个区间的结构。您将存储每个插槽转换的X值(总共N + 1个值)以及该区间的人口数量。
流式传输您的数据。记录前N + 1个值。如果流在此之前结束,那么很好,您已经加载了所有值,并且可以找到确切的中位数并返回它。否则,请使用这些值来定义您的第一个直方图。只需对值进行排序,然后使用这些值作为区间定义,每个区间的人口为1。拥有重复项(0宽度区间)是可以的。
现在流式传入新值。对于每个值,二分搜索以找到它所属的区间。在常见情况下,您只需增加该区间的人口并继续进行。如果样本超出了直方图的边缘(最高或最低),则将结束区间的范围扩展以包括它。当您的流完成时,通过找到两侧均具有相等人口的区间来找到中位数样本值,并线性插值剩余的区间宽度。
但这还不够...您仍然需要根据正在流式传输的数据调整直方图。当一个区间过满时,您正在失去该区间的子分布信息。您可以通过基于某些启发式方法进行适应来解决此问题...最简单和最强大的方法是如果一个区间达到某个特定阈值人口(例如10 * v / N,其中v =流中迄今为止看到的值的数量,N是区间数),则将该过度填充的区间分成两半。在区间的中点添加一个新值,将原始区间的一半分配给每个侧。但现在您有太多的区间,因此需要删除一个区间。一个好的启发式方法是找到人口和宽度乘积最小的区间。将其删除并与其左侧或右侧的邻居合并(哪个邻居本身的宽度和人口乘积最小)。完成!
请注意,合并或分割区间会丢失信息,但这是不可避免的...您只有固定的存储空间。
此算法很好,在处理所有类型的输入流时都能给出良好的结果。如果您有选择样本顺序的奢侈品,则随机样本最佳,因为这可以最小化分裂和合并。
该算法还允许您查询任何百分位数,而不仅仅是中位数,因为您具有完整的分布估计。
我在自己的代码中多次使用这种方法,主要用于调试日志...其中一些正在记录的统计数据具有未知分布。使用此算法,您无需提前猜测。
缺点是不等宽箱子意味着您必须为每个样本执行二进制搜索,因此您的净算法为O(NlogN)。