乐趣区

关于python:最简单实现-Python计算-数据流中的中位数-leetcode算法

一、问题形容:

中位数 是有序列表两头的数。
如果列表长度是偶数,中位数则是两头两个数的平均值。

例如,
[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

退出移动版