**题目描述:**如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值
题目链接: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大值等众多问题。