一、问题形容:
中位数 是有序列表两头的数。
如果列表长度是偶数,中位数则是两头两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个反对以下两种操作的数据结构:
- void addNum(int num) – 从数据流中增加一个整数到数据结构中。
- double findMedian() – 返回目前所有元素的中位数。
示例:
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
二、问题剖析
1、如果有一个数据流:[3, 1, 9, 25, 12,…]
每流入一个数据,先在列表尾部 append,再做排序,而后计算中位数,过程如下:
3 -> [3]
中位数:3
1 -> [3, 1] -> [1, 3]
中位数:(1+3)/2 = 2.0
9 -> [1, 3, 9] -> [1, 3, 9]
中位数:3
25 -> [1, 3, 9, 25] -> [1, 3, 9, 25]
中位数:(3+9)/2 = 6.0
12 -> [1, 3, 9, 25, 12] -> [1, 3, 9, 12, 25]
中位数:9
最初输入的中位数列表为:[3, 2.0, 3, 6.0, 9, …]
2、计算过程总结:先排序、再折半
如图所示:
可见,找出中位数分两种状况:
- 当列表长度为奇数:间接取两头数
- 当列表长度为偶数:计算最两头两数的平均数
3、根本实现
算法实现的办法很多,最直观的就是用列表的 sort(),再取中值即可。
def running_medians_naive(iterable):
medians = []
values = []
for i,x in enumerate(iterable):
values.append(x)
values.sort()
if i % 2 == 0:
medians.append(values[i//2])
else:
medians.append((values[i//2]+values[i//2+1])/2)
return medians
running_medians_naive([3, 1, 9, 25, 12])
察看以上算法实现的工夫复杂度,能够发现每次都要对列表进行 sort(),比拟耗时。
4、堆 Heap 实现
仔细观察计算过程,能够发现:取中位数只跟列表地方的 1 - 2 个值相干,如图所示:
跟堆 Heap 的思维十分类似,能够把中位数的左半折看成是大顶堆 max-heap、右半侧看成是小顶堆 min-heap,这样就能够用堆 Heap 来升高工夫复杂度了。
Python 实现由两局部组成:max-heap 堆 和 取中位数算法。
4.1、Max-heap 堆
class Heap:
def __init__(self, key=lambda x:x):
self.data = []
self.key = key
@staticmethod
def _parent(idx):
return (idx-1)//2
@staticmethod
def _left(idx):
return idx*2+1
@staticmethod
def _right(idx):
return idx*2+2
def heapify(self, idx=0):
while idx < len(self.data):
left = Heap._left(idx)
right = Heap._right(idx)
mid_idx = idx
if left < len(self.data) and self.key(self.data[left]) > self.key(self.data[mid_idx]):
mid_idx = left
if right < len(self.data) and self.key(self.data[right]) > self.key(self.data[mid_idx]):
mid_idx = right
if mid_idx != idx:
self.data[mid_idx], self.data[idx] = self.data[idx], self.data[mid_idx]
idx = mid_idx
else:
break
def add(self, x):
self.data.append(x)
idx = len(self.data) - 1
while idx > 0:
p = Heap._parent(idx)
if self.key(self.data[idx]) > self.key(self.data[p]):
self.data[idx], self.data[p] = self.data[p], self.data[idx]
idx = p
else:
break
def peek(self):
return self.data[0]
def pop(self):
ret = self.data[0]
self.data[0] = self.data[len(self.data)-1]
del self.data[len(self.data)-1]
self.heapify()
return ret
def __bool__(self):
return len(self.data) > 0
def __len__(self):
return len(self.data)
def __repr__(self):
return repr(self.data)
4.2、取中位数
def running_medians_heap(iterator):
medians = []
max_heap = Heap(lambda x:x)
min_heap = Heap(lambda x:-x)
for i, x in enumerate(iterator):
if i % 2 == 0:
max_heap.add(x)
min_heap.add(max_heap.pop())
medians.append(min_heap.peek())
else:
min_heap.add(x)
max_heap.add(min_heap.pop())
medians.append((max_heap.peek() + min_heap.peek()) / 2)
return medians
running_medians_heap([3, 1, 9, 25, 12])
具体算法原理,请参考视频:
https://www.bilibili.com/vide…
转载请注明出处,如有意见或倡议,请分割公众号或电邮:bjstorm#foxmail.com