题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用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 "
'''