堆的基本概念及常用操作

  • 完全二叉树:若设二叉树的深度为 n n ,除第n层外,其它各层 ( 1 1 n1) 的结点数都达到最大个数,第 n n 层所有的结点都连续集中在最左边,这就是完全二叉树

  • 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为n,且结点总数是 2n1 2 n − 1 ,则它就是满二叉树

  • 堆(heap)又被为优先队列(priority queue)。尽管名为优先队列,但堆并不是队列。回忆一下,在队列中,我们可以进行的限定操作是dequeue和enqueue。dequeue是按照进入队列的先后顺序来取出元素。而在堆中,我们不是按照元素进入队列的先后顺序取出元素的,而是按照元素的优先级取出元素
  • 堆的一个经典的实现是完全二叉树(complete binary tree)。这样实现的堆成为二叉堆(binary heap)。由于其它几种堆(二项式堆,斐波纳契堆等)用的较少,一般将二叉堆就简称为堆。
二叉堆满足二个特性:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆

当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆

堆的基本操作

堆的主要操作是插入删除最小(最大)元素(元素值本身为优先级键值,小元素享有高优先级)。在插入或者删除操作之后,我们必须保持该实现应有的性质: 1. 完全二叉树 2. 每个节点值都小于或等于它的子节点
以下的所有操作都以以最小堆为例,最大堆是同样的道理。

1. 堆的存储

一般都用数组来表示堆, i i 结点的父结点下标就为(i1)/2。它的左右子结点下标分别为 2i+1 2 ∗ i + 1 2i+2 2 ∗ i + 2 。如第0个结点左右子结点下标分别为1和2。
此处输入图片的描述

2. 插入

在插入操作的时候,将新插入的节点放在完全二叉树最后的位置,再和父节点比较。如果new节点比父节点小,那么交换两者。交换之后,继续和新的父节点比较…… 直到new节点不比父节点小,或者new节点成为根节点。这样得到的树,就恢复了堆的性质。
此处输入图片的描述

# insertion of a hep
def MinHeapFixup(a, i):
    """Fix up inplace from node i"""
    temp = a[i]
    # j is the index of parent node
    j = (i - 1) / 2
    while i != 0 and j >= 0:
        if a[j] < temp:
            break
        a[i] = a[j]
        i = j
        j = (i - 1) / 2
    a[i] = temp

def MinHeapAddNumber(a, num):
    """insert a number in heap"""
    n = len(a)
    a.append(num)
    MinHeapFixup(a, n)

if __name__ == '__main__':
    a = [1,2,3,4,5,6]
    MinHeapAddNumber(a, 4)
    print(a)

# 打印的结果是
[1, 2, 3, 4, 5, 6, 4]

这个在python中已有自带的官方库heapq实现

from heapq import *
x = [9,7,6,4,3,1]
# build heap
heapify(x)
print(x)
# insert
heappush(x,2)
print(x)

'''打印结果如下'''
# 原始堆
[1, 3, 6, 4, 7, 9]
# 插入2之后的结果
[1, 3, 2, 4, 7, 9, 6]
3. 删除

删除操作只能删除根节点。根节点删除后,我们会有两个子树,我们需要基于它们重构堆。 让最后一个节点last成为新的节点,从而构成一个新的二叉树。再将last节点不断的和子节点比较。如果last节点比两个子节点中小的那一个大,则和该子节点交换。直到last节点不大于任一子节点都小,或者last节点成为叶节点。
此处输入图片的描述

# deletion of a heap
def MinHeapFixdown(a, i):
    """fix down in place from node i"""
    n = len(a)
    temp = a[i]
    # j is the left child node
    j = 2*i + 1
    while j < n:
        if j+1 < n and a[j+1] < a[j]:
            j += 1
        if a[j] > temp:
            break
        a[i] = a[j]
        i = j
        j = 2*i + 1
    a[i] = temp

def MinheapDeleteNumber(a):
    """delete a number in heap"""
    n = len(a)
    a[0], a[n-1] = a[n-1], a[0]
    a.pop()
    MinHeapFixdown(a, 0)

if __name__ == '__main__':
    b = [1,2,6,7,4,13,8]
    MinheapDeleteNumber(b)
    print(b)

# 打印的结果是
[2, 4, 6, 7, 8, 13]

用heapq实现是

x = [1, 3, 2, 4, 7, 9, 6]
# delete
min_value = heappop(x)
print(min_value)
print(x)

'''打印的结果是'''
1                   # 弹出的最小元素
[2, 3, 6, 4, 7, 9]  # 删除后的结果
4. 堆化数组(建堆)

有了堆的插入和删除后,再考虑下如何对一个数据进行堆化操作。

# build MinHeap
def MakeMinHeap(a):
    n = len(a)
    for i in range(n/2-1, -1, -1):
        MinHeapFixdown(a, i)
if __name__ == '__main__':
    c = [9,5,3,1,6,7,3,10,8]
    MakeMinHeap(c)
    print(c)

# 打印的结果是
[1, 5, 3, 8, 6, 7, 3, 10, 9]

heapq直接使用heapify建堆

from heapq import *
x = [9,7,6,4,3,1]
# build heap
heapify(x)

'''打印的结果'''
[1, 3, 6, 4, 7, 9]  # 建好的堆
建堆的时间复杂度: O(n) O ( n )

堆排序

首先可以看到堆建好之后堆中第0个数据是堆中最小的数据。取出这个数据再执行下堆的删除操作。这样堆中第0个数据又是堆中最小的数据,重复上述步骤直至堆中只有一个数据时就直接取出这个数据。

# heap sort
def HeapSort(nums):
    ans = []
    # 建堆
    MakeMinHeap(nums)
    while len(nums) > 1:
        ans.append(nums[0])
        MinheapDeleteNumber(nums)
    ans.append(nums[0])
    return ans

if __name__ == '__main__':
    nums = [1,4,2,5,6,8,4]
    print(HeapSort(nums))

# 打印的结果是
[1, 2, 4, 4, 5, 6, 8]
nums = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
heapify(nums)
res = []
while nums:
    res.append(heappop(nums))
print res
'''打印的结果是'''
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
复杂度分析:
堆排序的时间复杂度:由于每次重新恢复堆的时间复杂度为 O(logn) O ( l o g n ) ,共 n1 n − 1 次重新恢复堆操作,再加上前面建立堆时间复杂度 O(n) O ( n ) 。二次操作时间相加还是 O(nlogn) O ( n l o g n )
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值