除非您的“价值”概念以某种可利用的方式受到限制,否则这是不可能的;和/或您可以对数据进行多次传递;和/或您也愿意将内容存储在磁盘上。假设您知道有 5 个值,都是不同的整数,并且您知道前 3 个是 5、6、7。中位数将是其中之一,但此时您不知道是哪一个,所以您必须记住他们都是。如果接下来是 1 和 2,则中位数为 5;如果接下来是 4 和 8,则为 6;如果接下来是 8 和 9,那就是 7。
这显然可以推广到任何奇数个值 range(i, i + 2*N+1),此时您已经看到了其中的第一个 N+1:中位数可以是第一个 N+1 中的任何一个,所以除非有一些关于价值观性质的可利用的东西,你必须在那时记住所有这些。
一个可利用的例子:你知道最多有 100 个 distinct 值。然后,您可以使用 dict 计算每个出现的次数,并根据分布的压缩表示轻松计算最后的中位数。
近似
由于已经提到的原因,这里一般没有“捷径”。但我将附上 Python 代码以获取合理的一次性逼近方法,详见"The Remedian: A Robust Averaging Method for Large Data Sets"。该论文还指出了其他近似方法。
关键:选择一个大于 1 的奇数 B。然后将连续的元素存储在缓冲区中,直到其中的 B 被记录。在这一点上,这些中位数进入下一个级别,并且缓冲区被清除。它们的中位数仍然是保留的那些B 元素的唯一记忆。
同样的模式也在更深的层次上继续存在:在B 的这些中位数中的B 中位数被记录后,那些的中位数进入下一个级别,第二个级缓冲区被清除。高级中位数仍然是进入其中的B**2 元素的唯一内存。
等等。在最坏的情况下,它可能需要存储B * log(N, B) 值,其中N 是元素的总数。在 Python 中,很容易对其进行编码,因此可以根据需要创建缓冲区,因此无需提前知道 N。
如果B >= N,该方法是精确的,但是您还存储了每个元素。如果B < N,它是中位数的近似值。有关详细信息,请参阅论文 - 它非常复杂。这是一个让它看起来非常好的案例;-)
>>> import random
>>> xs = [random.random() for i in range(1000001)]
>>> sorted(xs)[500000] # true median
0.5006315438367565
>>> w = MedianEst(11)
>>> for x in xs:
... w.add(x)
>>> w.get()
0.5008443883489089
也许令人惊讶的是,如果输入按排序顺序添加,情况会更糟:
>>> w.clear()
>>> for x in sorted(xs):
... w.add(x)
>>> w.get()
0.5021045181828147
用户小心!代码如下:
class MedianEst:
def __init__(self, B):
assert B > 1 and B & 1
self.B = B
self.half = B >> 1
self.clear()
def add(self, x):
for xs in self.a:
xs.append(x)
if len(xs) == self.B:
x = sorted(xs)[self.half]
xs.clear()
else:
break
else:
self.a.append([x])
def get(self):
total = 0
weight = 1
accum = []
for xs in self.a:
total += len(xs) * weight
accum.extend((x, weight) for x in xs)
weight *= self.B
# `total` elements in all
limit = total // 2 + 1
total = 0
for x, weight in sorted(accum):
total += weight
if total >= limit:
return x
def clear(self):
self.a = []