定义——参考
堆就是用数组实现的二叉树,所以它没有使用父指针或者子指针。堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。
堆总是满足下列性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。
下图是一个最大堆:
注意:堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。例如,在一个最大堆中,最大的那一个元素总是位于 index 0 的位置,但是最小的元素则未必是最后一个元素。–唯一能够保证的是最小的元素是一个叶节点,但是不确定是哪一个。
堆是非线性数据结构,相当于一维数组,有两个直接后继。
堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2)
对如何确定父节点和子节点呢?
parent(i) = floor((i - 1)/2)
left(i) = 2i + 1
right(i) = 2i + 2
堆的操作
1、shiftUp(): 如果一个节点比它的父节点大(最大堆)或者小(最小堆),那么需要将它同父节点交换位置。这样是这个节点在数组的位置上升。
2、shiftDown(): 如果一个节点比它的子节点小(最大堆)或者大(最小堆),那么需要将它向下移动。这个操作也称作“堆化(heapify)”。
构造最大二叉堆并进行堆排序
class Solution:
def sink(self,s,root):
if 2*root+1<len(s):
k = 2*root+2 if 2*root+2<len(s) and s[2*root+2]>s[2*root+1] else 2*root+1
if s[k] > s[root]:
s[k],s[root] = s[root],s[k]
self.sink(s,k)
def maxheap(self,s):
for i in range(len(s)//2-1,-1,-1):#一定得从下往上来
self.sink(s,i)
return s
def heap_sort(self,s):
last = len(s)-1
self.maxheap(s)#构造最大堆
while last >0:
s[0],s[last] = s[last],s[0]
s[:last] = self.maxheap(s[:last])#因对s的切片进行最大堆排序,一定要把排序结果赋值给s,不然就没有任何结果
last -= 1
return s
answer = Solution()
print(answer.maxheap([1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]))
print(answer.heap_sort([1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]))
如果一个堆有n个节点,那么他的高度是h=floor(log2n)
层数是ceil(log2n)
叶节点索引位于n//2和n-1之间
堆排序的原理
应用-数据流中的中位数
参考
题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
注意:数据流的数目是随时间变化而增加的,所以存放数据的容器的动态变化的
思路:
将数据容器分成两部分,令左边部分的所有数据都小于右边部分中的所有数据,将左边构造成一个最大堆,右边构造成一个最小堆,这样插入一个数的时间复杂度是O(logn),获取中位数的时间复杂度是O(1)
class Solution:
def __init__(self):
self.left = []#最大堆
self.right = []#最小堆
self.count = 0
def Insert(self,num):
if self.count&1 == 0:#容器总个数是偶数个,插入最小堆
if self.left and num<self.left[0]:#如果插入的数比最大堆中的某些数小,先把这个数插入最大堆,然后把最大堆的根节点取出来插入最小堆
self.left.append(num)
self.maxheap(self.left)
self.right.append(self.left.pop(0))
self.minheap(self.right)
else:
self.right.append(num)
self.minheap(self.right)
else:#如果容器总个数是奇数,插入最大堆
if self.right and num>self.right[0]:#如果这个数大于最小堆中的数
self.right.append(num)
self.minheap(self.right)
self.left.append(self.right.pop(0))
self.maxheap(self.left)
else:
self.left.append(num)
self.maxheap(self.left)
self.count += 1
def GetMedian(self):
if self.count & 1 == 0:#偶数个
return (self.left[0]+self.right[0])/2
else:
return self.right[0]
def maxheap(self,s):
length = len(s)
if not s or length<= 0:
return
if length == 1:
return s
for k in range((length-1)//2,-1,-1):#(length-1)//2
heap = False
while not heap and 2*k+1 <length:
index = 2*k+2 if 2*k+2<length and s[2*k+2]>s[2*k+1] else 2*k+1
if s[k] >s[index]:
heap = True#下层堆建好上面就不用管了
else:
s[k],s[index] = s[index],s[k]
k = index
return s
def minheap(self,s):
length = len(s)
if not s or length<=0:
return
if length == 1:
return s
for k in range(length//2-1,-1,-1):
heap = False
while not heap and 2*k+1<length:
index = 2*k+2 if 2*k+2<length and s[2*k+2]<s[2*k+1] else 2*k+1
if s[k] < s[index]:
heap = True
else:
s[k],s[index] = s[index],s[k]
k = index
return s
answer = Solution()
string = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]
for i in string:
answer.Insert(i)
print(answer.GetMedian())
打印二叉树的函数
def print_tree(self,array):
index = 0
depth = math.ceil(math.log2(len(array)))
sep = ' '
for i in range(depth):
offset = 2**i
print(sep*(2**(depth-i-1)-1),end = '')
line = array[index:index+offset]
for j,x in enumerate(line):
print('{:>{}}'.format(x,len(sep)),end='')
interval = 0 if i == 0 else 2**(depth-i)-1
if j<len(line)-1:
print(sep*interval,end='')
index+= offset
print()
shiftup函数
def shiftUp(self,s,data):
self.maxheap(s)
pos = len(s)
s.append(data)
while (pos-1)//2>=0 and data>s[(pos-1)//2]:
s[pos],s[(pos-1)//2] = s[(pos-1)//2],s[pos]
pos = (pos-1)//2
return s