算法导论——python实践(6.堆排序)

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.算法导论 机械工业出版社 第六章 堆排序。

猜你喜欢

转载自blog.csdn.net/weixin_42206504/article/details/82429502