数据流中的中位数

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

输入输出示例

输入:[5,2,3,4,1,6,7,0,8]
返回值:"5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "
说明:
数据流里面不断吐出的是5,2,3…,则得到的平均数分别为5/1,(5+2)/2,(5+2+3)/3…

解题思路

用最大堆和最小堆来解决该问题。最大堆的根节点是最大堆中最大的数,最小堆的根节点是最小堆中最小的数。插入的值先往最大堆存入,然后在往最小堆存入;最大堆存的是相对小的值,最小堆存的是相对大的值,最大堆与最小堆每次元素的差要么为1(最大堆先存,比最小堆多一个值),要么相等;相差一个数的情况就是奇数个数,此时中位数就是最大堆的根节点,相等的情况就是偶数个数,此时的中位数就是最大堆与最小堆的根节点值的平均。
具体实现:

  • 要完成最大堆和最小堆的创建和调整。
  • 最后插入的过程Insert(创建最大堆和最小堆,调整最大堆和最小堆):
    如果最大堆中的长度大于最小堆中的元素长度,则此时的数要存入最小堆中,但是在存入最小堆之前需要将该数与最大堆的根节点做比较,如果这个数小于最大堆的根节点,则需要与根节点的数做替换,然后用替换后的数存入到最小堆中,然后进行调整;否则,直接将数存入最小堆中;
    如果最大堆中的长度与最小堆的长度一致,则将数存入最大堆中,存入最大堆前需要与最小堆中的根节点做比较,如果比最小堆的根节点大,则需要需最小堆的根节点交换,用交换后的数存入最大堆中,然后进行调整;否则,直接将数存入最大堆中。
    注:每一次存数都需要计数加1
  • 获取中位数:如果最大堆的长度大于最小堆的长度,则结果为最大堆的根节点;如果最大堆的长度等于最小堆的长度,则结果为最小堆与最大堆根节点的平均

代码示例

排序方式,定义一个空数组结构;想数组中添加值,每次添加都去堆数组进行排序,计算数组中元素个数。如果为奇数个,则中位数返回中间值,如果是偶数个,中位数是中间两个值的平均

class Solution1:
    def __init__(self):
        self.s = []

    def Insert(self, num):
        self.s.append(num)

    def GetMedian(self):
        self.s.sort()
        sLen = len(self.s)
        index = sLen // 2
        if sLen%2==1:
            return self.s[index]
        else:
            return (self.s[index-1] + self.s[index]) / 2.0
   
    s1 = Solution1()
    for i in [5,2,3,4,1,6,7,0,8]:
        s1.Insert(i)
        print(s1.GetMedian(), end=" ")
# coding:utf-8

class Solution:
    def __init__(self):
        self.littleValueMaxHeap = []
        self.bigValueMinHeap = []
        self.maxHeapCount = 0
        self.minHeapCount = 0

    def Insert(self, num):
        # write code here
        '''
        因为是先往最大堆中存入数据,
        所以最大堆的元素个数要么大于最小堆,要么等于最小堆
        '''
        # 如果最大堆中的元素大于最小堆中的元素,向最小堆中加入元素
        if self.minHeapCount < self.maxHeapCount:
            self.minHeapCount += 1
            # 首先需要判断要存入的值与最大堆中顶点元素值的大小
            # 如果小于最大堆的顶点值,则需要两项替换,然后再去存入最小堆中
            if num < self.littleValueMaxHeap[0]:
                # tempNum存入原始最大堆的根节点
                tempNum = self.littleValueMaxHeap[0]
                # num替换最大堆的根节点
                # 交换完以后需要调整更新后的最大堆
                self.adjustMaxHeap(num)
                # 用原始最大堆根节点值插入最小堆,并调整最小堆
                self.createMinHeap(tempNum)
            # 否则相等的情况下如果最大堆为空,则存入最大堆,否则直接将新的值插入最小堆中
            else:
                self.createMinHeap(num)
        # 最大堆与最小堆元素相等,将新的值存入最大堆
        else:
            self.maxHeapCount += 1
            # 如果最大堆中为空,直接存入新的值,不需要调整
            if len(self.littleValueMaxHeap) == 0:
                self.createMaxHeap(num)
            # 如果最大堆中不为空
            else:
                # 要判断存入元素与最小堆顶点的值大小
                # 如果大于最小堆的顶点值,则需要两项替换,然后再去存入最大堆
                if self.bigValueMinHeap[0] < num:
                    # tempNum存入原始最小堆的根节点
                    tempNum = self.bigValueMinHeap[0]
                    # num替换最小堆的根节点
                    # 交换完以后需要调整更新后的最小堆
                    self.adjustMinHeap(num)
                    # 用原始最小堆根节点值插入最大堆,并调整最大堆
                    self.createMaxHeap(tempNum)
                # 否则将新的值直接存入最大堆中
                else:
                    self.createMaxHeap(num)


    def GetMedian(self):
        # write code here
        # 如果最大堆中的元素大于最小堆中的元素,说明整个是奇数个
        # 中位数直接返回最大堆中顶点元素即可
        if self.minHeapCount < self.maxHeapCount:
            return  self.littleValueMaxHeap[0]
        # 否则,说明两个堆数量一致,是偶数个,返回的是最大堆与最小堆顶点值的平均值
        else:
            return (self.littleValueMaxHeap[0] + self.bigValueMinHeap[0]) / 2



    # 创建存放小值的最大堆 满足上面大下面小
    def createMaxHeap(self, num):
        # 加元素到最后一位上
        self.littleValueMaxHeap.append(num)
        # 确定当前节点的索引值
        maxCurrentIndex = len(self.littleValueMaxHeap)-1
        # 当节点不为根节点时循环遍历
        while maxCurrentIndex:
            # 确定其父节点
            parentIndex = (maxCurrentIndex-1)//2
            # 判断父节点与当前节点的值的大小,如果当前节点的值大,则进行两项值的替换
            if self.littleValueMaxHeap[parentIndex] < self.littleValueMaxHeap[maxCurrentIndex]:
                self.littleValueMaxHeap[parentIndex], self.littleValueMaxHeap[maxCurrentIndex] = self.littleValueMaxHeap[maxCurrentIndex], self.littleValueMaxHeap[parentIndex]
                maxCurrentIndex = parentIndex
            # 否则,不变换,直接跳出循环
            else:
                break


    # 创建存放大值的最小堆  满足上面小下面大
    def createMinHeap(self, num):
        self.bigValueMinHeap.append(num)
        minCurrentIndex = len(self.bigValueMinHeap)-1
        while minCurrentIndex:
            parentIndex = (minCurrentIndex-1)//2
            # 如果当前节点的值小于父节点的值,则需要进行两项值的替换
            if self.bigValueMinHeap[minCurrentIndex] < self.bigValueMinHeap[parentIndex]:
                self.bigValueMinHeap[minCurrentIndex], self.bigValueMinHeap[parentIndex] = self.bigValueMinHeap[parentIndex], self.bigValueMinHeap[minCurrentIndex]
                minCurrentIndex = parentIndex
            else:
                break


    # 存入新的值到最大堆的头结点进行调整
    def adjustMaxHeap(self, num):
        # 如果要存入的num值比最大堆原本顶点的值小,则替换顶点的值
        if num < self.littleValueMaxHeap[0]:
            self.littleValueMaxHeap[0] = num
            # 当前节点值的索引
            numIndex = 0
            # 要比较的下面左右节点中最大的值的索引
            # largerIndex = 0
            maxLen = len(self.littleValueMaxHeap)
            while numIndex < maxLen:
                leftIndex = numIndex*2+1
                rightIndex = numIndex*2+1
                # 有右节点
                if rightIndex < maxLen:
                    # 比较左右节点,记录更大的
                    largerIndex = leftIndex if self.littleValueMaxHeap[rightIndex] < self.littleValueMaxHeap[leftIndex] else rightIndex
                # 没有右节点
                elif leftIndex < maxLen:
                    largerIndex = leftIndex
                else:
                    break
                # 如果下面的值(左右节点中最大的)大于上面的值(当前比较节点)
                if self.littleValueMaxHeap[numIndex] < self.littleValueMaxHeap[largerIndex]:
                    self.littleValueMaxHeap[numIndex], self.littleValueMaxHeap[largerIndex] = self.littleValueMaxHeap[largerIndex], self.littleValueMaxHeap[numIndex]
                    numIndex = largerIndex
                else:
                    break

    #存入新的值到最大堆的头结点进行调整
    def adjustMinHeap(self, num):
        # 如果要存入的num值比最小堆原本顶点的值大,则替换顶点的值
        if num < self.bigValueMinHeap[0]:
            self.bigValueMinHeap[0] = num
            # 当前节点值的索引
            numIndex = 0
            # 要比较的下面左右节点中最小的值的索引
            # smallerIndex = 0
            minLen = len(self.bigValueMinHeap)
            while numIndex < minLen:
                leftIndex = numIndex * 2 + 1
                rightIndex = numIndex * 2 + 1
                # 有右节点
                if rightIndex < minLen:
                    # 比较左右节点值的大小,记录更小的
                    smallerIndex = rightIndex if self.bigValueMinHeap[rightIndex] < self.littleValueMaxHeap[leftIndex] else leftIndex
                # 没有右节点
                elif leftIndex < minLen:
                    smallerIndex = leftIndex
                else:
                    break
                # 如果下面的值(左右节点中最小的)小于上面的值(当前比较节点),进行替换
                if self.bigValueMinHeap[smallerIndex] < self.bigValueMinHeap[numIndex]:
                    self.bigValueMinHeap[numIndex], self.bigValueMinHeap[smallerIndex] = self.bigValueMinHeap[smallerIndex], self.littleValueMaxHeap[numIndex]
                    numIndex = smallerIndex
                else:
                    break
if __name__ == '__main__':
    s = Solution()
    for i in [5,2,3,4,1,6,7,0,8]:
        s.Insert(i)
        print(s.GetMedian(), end=" ")

# "5.00 3.50 3.00 3.50 3.00 3.50 4.00 3.50 4.00 "
'''
 "616.00 1356.50 616.00 1356.50 957.00 1527.00 2097.00 1527.00 1225.00
 1661.00 1225.00 1149.50 1225.00 1149.50 1225.00 1454.50 1225.00 1440.50
 1656.00 1530.00 1656.00 1530.00 1656.00 1530.00 1404.00 1314.50 1404.00
 1530.00 1404.00 1426.50 1404.00 1314.50 1225.00 1149.50 1074.00 1149.50
 1225.00 1149.50 1225.00 1149.50 1074.00 1149.50 1087.00 1080.50 1074.00
 1022.00 1074.00 1022.00 1046.00 1008.00 "
 '''

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值