一、问题形容:
中位数 是有序列表两头的数。
如果列表长度是偶数,中位数则是两头两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个反对以下两种操作的数据结构:
- void addNum(int num) - 从数据流中增加一个整数到数据结构中。
- double findMedian() - 返回目前所有元素的中位数。
示例:
addNum(1)addNum(2)findMedian() -> 1.5addNum(3) findMedian() -> 2
二、问题剖析
1、如果有一个数据流:[3, 1, 9, 25, 12,… ]
每流入一个数据,先在列表尾部append,再做排序,而后计算中位数,过程如下:
3 -> [3]中位数:31 -> [3, 1] -> [1, 3]中位数:(1+3)/2 = 2.09 -> [1, 3, 9] -> [1, 3, 9]中位数:325 -> [1, 3, 9, 25] -> [1, 3, 9, 25]中位数:(3+9)/2 = 6.012 -> [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 mediansrunning_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 mediansrunning_medians_heap([3, 1, 9, 25, 12])
具体算法原理,请参考视频:
https://www.bilibili.com/vide...
转载请注明出处,如有意见或倡议,请分割公众号或电邮:bjstorm#foxmail.com