1、堆的组织形式、建堆
简单来讲就是将数组按照完全二叉树的形式排列。叶节点的元素个数最多为2^(n-1)次方,其中n为堆高度。
最大堆:某一根叶节点的元素小于等于根节点的数值。通常用于排序
最小堆:某一根叶节点的元素大于等于根节点的数值。通常用于构造优先队列
首先建立最大堆: 函数输入参数为一个序列和序列的某一下标。
对于某一下标,首先求出该节点下的左子树和右子树下标,左子树和右子树分别于父节点对比,大者的下标标记为largest,如果最大值的下标发生了改变(不等于i)交换两者元素,继续重新判断子树下面是否满足最大堆条件
def max_heapify(a,i):
l=2*i+1 #子节点下标
r=2*(i+1)
if l<=len(a)-1 and a[l]>a[i]:
largest=l
else:
largest=i
if r<=len(a)-1 and a[r]>a[largest]:
largest=r
if largest !=i:
a[i],a[largest]=a[largest],a[i]
max_heapify(a,largest)
def build_max_heap(a):
for i in range((len(a)-1)//2,-1,-1):
max_heapify(a,i) #这里是继续判断子树下面的子树是否也满足最大堆条件
2、堆排序
现在的重点是如何把堆上的元素取下来。注意到最大堆的根节点a[0]必定是最大值。通过把它与A[n-1]进行互换,然后去掉最大元素,再次调用max_heapify(a,0),实际上这个函数max_heapify(a,i)能选择出下标比i大的元素中最大的那个元素作为根节点。重复调用上面的过程就能一步一步把最大的元素取出来。
a=[4,1,3,2,16,9,10,14,8,7]
def heapsort(a):
b=a.copy()
c=[]
build_max_heap(b)
c.append(b[0])
for i in range(len(a)-1,0,-1):
del b[0]
max_heapify(b,0)
c.append(b[0])
return c
print( heapsort(a))
a=heapsort(a)
运行结果:[16, 14, 10, 9, 8, 7, 4, 3, 2, 1]
3、优先队列
基于最大堆实现最大优先队列:在共享计算机系统的作业调度中,最大优先队列记录将要执行的各个作业以及他们之间的相对优先级。当一个作业完成或者被中断后,调用EXTRACT_MAX从所有的等待作业中,选出最高优先级的作业来执行。在任何时候,调度器都可以调用insert把一个作业加入到队列中来。
主要实现以下功能:
INSERT(S,x):把元素x插入集合S中。
MAXIMUN(S):返回S中具有最大优先序号的元素。
EXTRACT_MAX(S):去掉并返回S中的具有最大优先序号的元素。
INCREASE_KEY(S,x,k):将元素x的优先序号增加到k,这里假设k的值不小于x的优先序号。
a=heapsort(a)
def heap_maximum(a):
return a[0]
def heap_rxtract_max(a):
if len(a)<1:
ex1=Exception("heap underflow") #Exception(*args)
raise ex1
max=a[0]
a=a[1:len(a)]
max_heapify(a,0)
return max,a
def heap_incease_key(a,i,key):
if key<a[i]:
ex2=Exception('new key is smaller current key')
raise ex2
a[i]=key
while i>0 and a[(i-1)//2]<a[i]:#父节点比a[i]小的话更新父节点,始终满足最大堆性质
a[i],a[(i-1)//2]=a[(i-1)//2],a[i]
i=(i-1)//2
return a
def heap_insert(a,key):
a.append(float('-inf'))
heap_incease_key(a,len(a)-1,key)
print(heap_maximum(a))
print(heap_rxtract_max(a))
print(heap_incease_key(a,3,12))
输出结果:
16
(16, [14, 10, 9, 8, 7, 4, 3, 2, 1])
[16, 14, 10, 12, 8, 7, 4, 3, 2, 1]
这里有一点需要注意的是:列表作为函数参数传入时,对列表进行切片操作(这相当于是复制[:])时不会改变原来列表的值;但是在进行下标修改时,由于列表是可变参数,会改变原来列表的值。
这里我们自定义个两个异常,当发生特定条件是抛出异常。
4、最小堆
6.5-3 要求利用最小堆实现最小优先队列
在最大堆的基础上把大于小于号修改一下即可。
def min_heapify(a,i):
l=2*i+1 #子节点下标
r=2*(i+1)
if l<=len(a)-1 and a[l]<a[i]:
largest=l
else:
largest=i
if r<=len(a)-1 and a[r]<a[largest]:
largest=r
if largest !=i:
a[i],a[largest]=a[largest],a[i]
max_heapify(a,largest)
def build_min_heap(a):
for i in range((len(a)-1)//2,-1,-1):
min_heapify(a,i) #这里是继续判断子树下面的子树是否也满足最大堆条件
a=[4,1,3,2,16,9,10,14,8,7]
def heapsort(a):
b=a.copy()
c=[]
build_min_heap(b)
c.append(b[0])
for i in range(len(a)-1,0,-1):
del b[0]
min_heapify(b,0)
c.append(b[0])
return c
print( heapsort(a))
a=heapsort(a)
a=heapsort(a)
def heap_minimum(a):
return a[0]
def heap_erxtract_min(a):
if len(a)<1:
ex1=Exception("heap underflow") #Exception(*args)
raise ex1
min=a[0]
a=a[1:len(a)]
min_heapify(a,0)
return min,a
def heap_decease_key(a,i,key):
if key>a[i]:
ex2=Exception('new key is biger current key')
raise ex2
a[i]=key
while i>0 and a[(i-1)//2]>a[i]:#父节点比a[i]大的话更新父节点,始终满足最小堆性质
a[i],a[(i-1)//2]=a[(i-1)//2],a[i]
i=(i-1)//2
return a
def heap_insert(a,key):
a.append(float('inf'))
heap_decease_key(a,len(a)-1,key)
print(heap_minimum(a))
print(heap_erxtract_min(a))
print(heap_decease_key(a,8,12))
5、部分习题
6.5-8 实现将结点i从堆A中删除。
将被删除的结点换成inf无穷大,利用切片操作删除inf元素,调用max_heapify使得满足最大堆性质。
def heap_delete(a,i):
heap_decease_key(a,i,float('inf'))
a[0]=a[-1]
a=a[0:-1]
max_heapify(a,0)
return a
思考题6.1 用插入的方法建堆
因为在heap_insert函数调用了heap_increase_key函数,该函数使得序列始终满足最大堆性质。因此可用插入法,每插入一个元素就进行重新建堆操作,使得满足最大堆性质。
def build_max_heap2(a):
b=[]
for i in range(0,len(a)):
heap_insert(b,a[i])
return b
思考题2 对d叉堆的分析
def dmax_heapify(a,i,d):
l=[]
for k in range(0,d):
if (d*i+k+1)<=len(a)-1: #如果索引不会大于a的索引,就可以往L中扩展元素
l.append(d*i+k+1) #存储子节点的索引值L数组
num=float('-inf')
flag=0
largest=i
for k in range(0,len(l)):
if a[l[k]]>num:
num=a[l[k]] #子节点中的最大值
flag=l[k] #子节点中的最大值的索引
if num>a[i]:
largest=flag
if largest !=i:
a[i],a[largest]=a[largest],a[i]
dmax_heapify(a,largest,d)
def build_dmax_heap(a,d):
for i in range((len(a)-1)//d,-1,-1):
dmax_heapify(a,i,d) #这里是继续判断子树下面的子树是否也满足最大堆条件
a=[0,1,2,3,4,5,63,7,8,9,10,11,12]
build_dmax_heap(a,3)
print(a)
def extract_dmax(a,d):
if len(a)<1:
ex1=Exception("heap underflow") #Exception(*args)
raise ex1
max=a[0]
a=a[1:len(a)]
dmax_heapify(a,0,d)
return max,a
def increase_key(a,i,k,d):
if k<a[i]:
ex2=Exception('new key is smaller than current key')
raise ex2
a[i]=k
while i>0 and a[(i-1)//d]<a[i]:#父节点比a[i]小的话更新父节点,始终满足最大堆性质
a[i],a[(i-1)//2]=a[(i-1)//d],a[i]
i=(i-1)//d
return a
def heap_insert(a,key,d):
a.append(float('-inf'))
increase_key(a,len(a)-1,key,d)
输出结果:[63, 5, 9, 12, 4, 0, 1, 7, 8, 2, 10, 11, 3]满足3叉堆的性质。具体思路:首先创建了一个序列用于存储子节点的索引值,这里进行了一个判断,如果算出来的子序列的索引值大于len(a)-1,就不对l序列进行扩展。然后找出子序列中最大的那个值,num存储子节点中的最大值,flag存储最大值的索引值,最后进行判断,如果largest的值不等于i,说明父子结点进行了交换,保持最大堆性质,之后递归调用dmax_heapify函数,保证一开始的父节点(此时已经做过某一更高层数的父节点的子节点了)也满足最大堆条件。build——dmax_heap函数用于对所有的非叶节点进行函数dmax_heapify调用。
6-3 young氏矩阵的构建以及实现查找功能
杨氏矩阵,每一行元素的数据都是从左至右的排序,每一列数据都是从上至下的排序。可以存在一些值为无穷大的数据项,表示那些不存在的元素
# coding:utf-8
def build_tableau(arr):
Max_Number=float('inf')
import math
n=int(math.sqrt(len(arr)))
m=n
if n**2<len(arr):
m+=1
table=[[Max_Number for x in range(n)] for y in range(m)]
temp=sorted(arr)
for i in range(m*n-len(arr)):
temp.append(float('inf'))
index=0
for i in range(m):
for j in range(n):
table[i][j]=temp[index]
index+=1
return table
a=[1,2,3,4,5,6,7,8,9]
def tableau_search(table,target):
m=len(table)
n=len(table[0])
i=m-1
j=0
while 0<=i<m and 0<=j<n:
if table[i][j]==target:
return (i,j)
elif table[i][j]>target:
i-=1 #利用杨氏矩阵的性质,大小是从左至右和从上至下排列
else:
j+=1
return 'No Such Element'
print(tableau_search(build_tableau(a),8))
参考文献:
1.算法导论 机械工业出版社 第六章 堆排序。