使用迭代、递归以及大小根堆三种方法实现获取数据流的中位数——python实现

**题目描述:**如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值
题目链接:leetcode
因为要获取中位数,所以简单的思路是将得到的元素排序,排序后直接索引获得中位数。而此题的难度等级是困难,所以想使用简单的遍历来添加元素显然是无法满足时间复杂度的要求。
这里提供了三种解题思路:
1、采用二分迭代的思路对元素进行有序添加
2、采用二分递归的思路对元素进行有序添加
3、采用两个堆,一个大根堆和一个小根堆。大根堆用于存储一半较小的元素集合,小根堆用于存储另一半较大的元素集合。这里的难点是在添加新元素时,大小根堆的操作要分类考虑的情况较多。
这里为了方便小伙伴们直接使用,本人一次性将代码展现出来,不懂的地方可以看注释

class MedianFinder:
    def __init__(self):
        self.data = []          #作为二分法的元素存储容器
        self.bigHeap = []       #作为堆方法的存储容器
        self.smallHeap = []

    #使用二分递归添加元素
    def addNum_recurse(self,num):
        def recurse(lis, sta, end, target):
            if sta >= end:
                if lis[sta]>target:          #插入位置判断
                    lis.insert(sta,target)
                else:
                    lis.insert(sta+1,target)
                return
            mid = (sta + end)//2
            if lis[mid]>target:
                recurse(lis,sta,mid-1,target)
            elif lis[mid]<target:
                recurse(lis,mid+1,end,target)
            else:
                lis.insert(mid,target)
            return

        if self.data == []:
            self.data.append(num)
        else:
            recurse(self.data,0,len(self.data)-1,num)
        print(self.data)

    #使用二分迭代法有序添加元素
    def addNum_iteration(self,data,num):
        if data == []:
            data.append(num)
            return
        head = 0
        trail = len(data)-1
        mid = (head+trail)//2
        while head < trail:
            if data[mid] == num:
                data.insert(mid,num)
                return
            elif data[mid] > num:
                trail = mid-1
            else:
                head = mid+1
            mid = (head+trail)//2
        if data[head] > num:        # 插入位置判断
            data.insert(head, num)
        else:
            data.insert(head + 1, num)
            return


    #创建两个堆,一个大根堆一个小根堆,大根堆存储等于或小于中位数的值,小根堆来存储大于中位数的值
    def addNum_heap(self,num):
        if self.bigHeap == []:
            self.bigHeap.append(num)
            return
        if self.smallHeap == []:
            if num > self.bigHeap[0]:
                self.smallHeap.append(num)
            else:
                self.smallHeap.append(self.bigHeap.pop())
                self.bigHeap.append(num)
            return

        #大根堆添加元素:从下到上调整根堆
        def  add_bigHeap(bigHeap,num):
            bigHeap.append(num)
            adNode = len(bigHeap)-1
            while ((adNode-1)//2)>=0:
                if bigHeap[adNode]>bigHeap[(adNode-1)//2]:
                    bigHeap[adNode], bigHeap[(adNode-1)//2] = bigHeap[(adNode-1)//2], bigHeap[adNode]
                    adNode = (adNode-1)//2
                else:
                    break

        #返回最大值并调整大根堆:交换根节点和最后一个节点,剔除最后一个节点作为返回最大值,对堆从上到下进行调整成为大根堆
        def adjust_bigHeap(bigHeap):
            bigHeap[0],bigHeap[-1]=bigHeap[-1],bigHeap[0]
            val = bigHeap.pop(-1)
            point = 0
            while (point*2+1<len(bigHeap)):        #截止条件为当前节点为叶子节点
                if point*2+2<len(bigHeap):         #当父节点拥有左右节点时
                    if bigHeap[point*2+1] == max(bigHeap[point],bigHeap[point*2+1],bigHeap[point*2+2]):
                        bigHeap[point], bigHeap[point * 2 + 1] = bigHeap[point * 2 + 1], bigHeap[point]
                        point = point * 2 + 1
                        continue
                    if bigHeap[point*2+2] == max(bigHeap[point], bigHeap[point * 2+1], bigHeap[point * 2+2]):
                        bigHeap[point], bigHeap[point * 2 + 2] = bigHeap[point * 2 + 2], bigHeap[point]
                        point = point * 2 + 2
                        continue
                    else:
                        break
                else:                              #当父节点只拥有左节点时
                    if bigHeap[point*2+1] == max(bigHeap[point],bigHeap[point*2+1]):
                        bigHeap[point], bigHeap[point * 2 + 1] = bigHeap[point * 2 + 1], bigHeap[point]
                        point = point * 2 + 1
                        continue
                    else:
                        break
            return val

        #小根堆添加元素:从下到上调整根堆
        def add_smallHeap(smallHeap,num):
            smallHeap.append(num)
            adNode = len(smallHeap)-1
            while ((adNode-1)//2)>=0:
                if smallHeap[adNode] < smallHeap[(adNode-1) // 2]:
                    smallHeap[adNode], smallHeap[(adNode - 1) // 2] = smallHeap[(adNode - 1) // 2], smallHeap[adNode]
                    adNode = (adNode - 1) // 2
                else:
                    break

        #返回小根堆最小值并调整小根堆,原理和大根堆调整相似
        def adjust_smallHeap(smallHeap):
            smallHeap[0],smallHeap[-1] = smallHeap[-1],smallHeap[0]
            val = smallHeap.pop(-1)
            point = 0
            while point*2+1<len(smallHeap):    ##不存在左子树时停止
                if point*2+2 < len(smallHeap):     #如果存在右子树
                    if smallHeap[point*2+1] == min(smallHeap[point],smallHeap[point*2+1],smallHeap[point*2+2]):   #如果父节点和子节点的最小值在左子节点
                        smallHeap[point],smallHeap[point*2+1] = smallHeap[point*2+1],smallHeap[point]             #交换父节点和左子节点
                        point = point*2+1
                        continue
                    if smallHeap[point*2+2] == min(smallHeap[point],smallHeap[point*2+1],smallHeap[point*2+2]):    #如果父节点和子节点的最小值在右子节点
                        smallHeap[point],smallHeap[point*2+2] = smallHeap[point*2+2],smallHeap[point]             #交换父节点和右子节点
                        point = point * 2 + 2
                        continue
                    else:
                        break
                else:
                    if smallHeap[point * 2 + 1] == min(smallHeap[point], smallHeap[point * 2 + 1]):  # 如果父节点和子节点的最小值在左子节点
                        smallHeap[point], smallHeap[point * 2 + 1] = smallHeap[point * 2 + 1], smallHeap[point]  # 交换父节点和左子节点
                        point = point * 2 + 1
                    else:
                        break
            return val

        if num <= self.bigHeap[0]:      #当新插入的元素小于大根堆最大值
            if len(self.bigHeap) == len(self.smallHeap):        #当大根堆元素个数和小根堆元素个数一致时
                add_bigHeap(self.bigHeap,num)
            else:                                               #当大根堆元素个数和小根堆元素个数不一致时
                add_smallHeap(self.smallHeap,adjust_bigHeap(self.bigHeap))
                add_bigHeap(self.bigHeap,num)
        elif num > self.smallHeap[0]:   #当新插入的元素大于小根堆最小值
            if len(self.bigHeap) == len(self.smallHeap):        #当大根堆元素个数和小根堆元素个数一致时
                add_bigHeap(self.bigHeap,adjust_smallHeap(self.smallHeap))
                add_smallHeap(self.smallHeap,num)
            else:                                               #当大根堆元素个数和小根堆元素个数不一致时
                add_smallHeap(self.smallHeap,num)
        else:                           #当新插入元素介于大根堆最大值和小根堆最小值时
            if len(self.bigHeap) == len(self.smallHeap):        #当大根堆元素个数和小根堆元素个数一致时
                add_bigHeap(self.bigHeap, num)
            else:                                               #当大根堆元素个数和小根堆元素个数不一致时
                add_smallHeap(self.smallHeap,num)

    def findMiddle_dichotomy(self):        #使用二分法两种方法的返回中位数
        lens = len(self.data)
        if lens == 0:
            return None
        else:
            mid = lens//2
            return self.data[mid] if lens%2==1 else (self.data[mid]+self.data[mid-1])/2

    def findMiddle_heap(self):            #使用大小根堆的方法返回中位数
        if len(self.bigHeap) == len(self.smallHeap):
            return (self.bigHeap[0]+self.smallHeap[0])/2
        else:
            return self.bigHeap[0]

if __name__ == "__main__":
    a = list(range(11))
    find = MedianFinder()
    for i in a:
        find.addNum_recurse(i)
        # find.addNum_iteration(find.data,i)
        print("中位数:", find.findMiddle_dichotomy())
        # find.addNum_heap(i)
        # print("中位数:",find.findMiddle_heap())

**总结:**相对于使用二分法解决,使用大小堆的思路较为复杂,代码也较为繁琐,但是这种方法也比较能开拓思维。大小根堆的思想能帮助我们解决获取最大值、最小值以及第K大值等众多问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值