《算法导论》堆排序和优先队列

堆是一种树形结构,通常基于完全二叉树实现最大堆或最小堆。

堆排序最坏的时间复杂度为O(nlogn),和归并排序一样;又和插入排序一样,属于原地排序;堆排序综合了两种算法的优势。

堆结构可以用来实现海量数据的Top-N排序,也可以用来实现优先队列(Priority Queue)。


堆排序

实现要点:

  • 堆基于完全二叉树实现,可以用数组存储,容易获得当前节点i的父节点和左右子节点的索引。
  • 这里以最大堆为例,即堆中任何一个节点的值都大于其子树中的值。
  • 实现的主要操作:
    • 维护最大堆的特性,max_heapify(i),代价O(logn)
    • 从无序数组建最大堆,build_max_heap(),代价O(n)
    • 堆排序,heap_sort(),代价O(nlogn)

max_heapify(i)

这个函数用来维护最大堆的特性,本质是下溯(percolate down,《STL源码剖析》),检查当前节点i的值是否大于其左右子节点的值。如果不满足,则和左右节点中的最大值交换,使得三者中的最大值放在根节点,下放的原根节点继续递归调用,检查在新的位置是否大于新的左右子节点的值。

注意,这个函数保证以i为根的子树满足最大堆,前提是i的左右子节点的两棵子树都是最大堆。如果不满足,调用一次max_heapify(i)并不能确保整个i节点的子树为最大堆。

def max_heapify(self, i):
    """
        if A[i] < A[left_child(i)] or A[i] < A[right_child(i)]
        percolate down
        T(n) = O(log n)
    """
    l = self.left_child(i)
    r = self.right_child(i)
    if l < self.heap_size and self.heap[i] < self.heap[l]:
        largest = l
    else:
        largest = i
    if r < self.heap_size and self.heap[largest] < self.heap[r]:
        largest = r

    if largest != i:
        # swap heap[largest] <-> heap[i]
        self.heap[i], self.heap[largest] = self.heap[largest], self.heap[i]
        self.max_heapify(largest) # decision
    return self.heap

build_max_heap

建堆操作可以基于max_heapify,对非叶节点,自底向上执行max_heapify调整堆即可。

考虑到上述的注意,建堆操作,下标i必须要从最后一个非叶节点(heap_size - 1)/2递减到根节点。否则从上到下,并不能保证底部的节点满足最大堆。(《算法导论》习题6.3-2)

def build_max_heap(self):
    for i in xrange((self.heap_size - 1)/2, -1, -1):
        self.max_heapify(i)
    return self.heap

heap_sort()

堆排序的前提是建好最大堆,每次取走堆的根节点(即堆中的最大值),重新用max_heapify调整堆,又取走根节点,能保证产生从大到小的元素。由于堆的动态调整复杂度不高,取决于堆的大小,能应对海量大数据的排序任务。如在1TB日志数据中找出Top-N大的数据,海量数据不能全部加载在内存中,可以借助堆来,实现动态的加载数据并产生有序的输出。

具体实现:

  1. 取根节点,和堆末节点交换。从后往前遍历堆的节点,取出堆的根节点(堆最大值),和堆的最后一个节点交换,从堆中删除最后一个节点,即取出的最大值(heap_size–)。

  2. 重新调整最大堆。从堆末换到根的节点一般都是当前堆里的较小值(不是BST,不一定最小),当前堆里除了根节点,其他均满足最大堆(原来就满足),所以只需要对根节点执行max_heapify,下放到合适的位置,实现重新调整堆。

重复上述步骤,在原来的数组中,前面部分的堆越来越小,后面输出的排好序的元素越来越多,直到遍历完最前面的根节点,原来的数组便是排好序(升序)的序列。

def heap_sort(self):
    """
        Build max_heap for list A
        return ascending sorted A
    """
    # self.build_max_heap() # build max heap
    for i in xrange(self.heap_size - 1, 0, -1):
        self.heap[0], self.heap[i] = self.heap[i], self.heap[0]
        self.heap_size -= 1
        self.max_heapify(0)
    return self.heap

优先队列

这里的优先级队列是基于最大堆实现的,加上操作:
- 出队dequeue(),代价O(logn)
- 入队enqueue(key),代价O(logn)

优先队列的出入队和传统队列一样:出队时在队头取出元素,入队时在队尾添加元素。但不同的是,队列的顺序是取决于排序的关键字大小,即所谓的优先级,跟入队顺序无关。因此,每次出队和入队操作后,都要对队列按照优先级重新排序。

dequeue

出队操作和堆排序相似,取出当前的堆的根节点,然后把堆末节点换到根节点,执行max_heapify,调整最大堆。

def dequeue(self):
    """
        HEAP-EXTRACT-MAX(A) 
        Similar to heap sort
    """
    if self.heap_size < 1:
        print "heap underflow"

    max_key = self.heap[0]
    self.heap[0] = self.heap[self.heap_size - 1]
    self.heap.pop(-1)
    self.heap_size -= 1
    self.max_heapify(0)
    return max_key

enqueue(key)

入队操作,则要在堆序列的末尾加上新入队的元素,并重新调整最大堆。

调整的方法和max_heapify相反,实现的是对堆末元素的上溯(percolate up,《STL源码剖析》)。具体方法:比较新加入节点的值和其父节点的值,如果比父节点大,则要和父节点交换位置,实现上移,然后继续判断上移后的节点(i = parent(i))和新的父节点的大小。

def enqueue(self, key):
    """
        MAX-HEAP-INSERT(A, key) + HEAP-INCREASE-KEY(A, i, key)
    """
    self.heap.append(key)
    self.heap_size += 1
    i = self.heap_size - 1
    while i > 0 and self.heap[self.parent(i)] < self.heap[i]:
         self.heap[i], self.heap[self.parent(i)] = self.heap[self.parent(i)], self.heap[i] 
         i = self.parent(i)

完整的python实现代码如下:

"""
    Heap: complete binary tree

    @author: Shangru 
"""

class Heap(object):

    def __init__(self, A):
        self.heap_size = len(A)
        self.heap = A
        self.build_max_heap()

    def parent(self, i):
        return (i - 1)/2

    def left_child(self, i):
        return 2*i + 1

    def right_child(self, i):
        return 2*i + 2

    def build_max_heap(self):
        for i in xrange((self.heap_size - 1)/2, -1, -1):
            self.max_heapify(i)
        return self.heap

    def max_heapify(self, i):
        """
            if A[i] < A[left_child(i)] or A[i] < A[right_child(i)]
            percolate down
            T(n) = O(log n)
        """
        l = self.left_child(i)
        r = self.right_child(i)
        if l < self.heap_size and self.heap[i] < self.heap[l]:
            largest = l
        else:
            largest = i
        if r < self.heap_size and self.heap[largest] < self.heap[r]:
            largest = r

        if largest != i:
            # swap heap[largest] <-> heap[i]
            self.heap[i], self.heap[largest] = self.heap[largest], self.heap[i]
            self.max_heapify(largest) # decision
        return self.heap

    def heap_sort(self):
        """
            Build max_heap for list A
            return ascending sorted A
        """
        # self.build_max_heap() # build max heap
        for i in xrange(self.heap_size - 1, 0, -1):
            self.heap[0], self.heap[i] = self.heap[i], self.heap[0]
            self.heap_size -= 1
            self.max_heapify(0)
        return self.heap

    # ======= priority queue =======
    def dequeue(self):
        """
            HEAP-EXTRACT-MAX(A) 
            Similar to heap sort
        """
        if self.heap_size < 1:
            print "heap underflow"

        max_key = self.heap[0]
        self.heap[0] = self.heap[self.heap_size - 1]
        self.heap.pop(-1)
        self.heap_size -= 1
        self.max_heapify(0)
        return max_key

    def enqueue(self, key):
        """
            MAX-HEAP-INSERT(A, key) + HEAP-INCREASE-KEY(A, i, key)
        """
        self.heap.append(key)
        self.heap_size += 1
        i = self.heap_size - 1
        while i > 0 and self.heap[self.parent(i)] < self.heap[i]:
             self.heap[i], self.heap[self.parent(i)] = self.heap[self.parent(i)], self.heap[i] 
             i = self.parent(i)

if __name__ == '__main__':

    # test build_heap
    h = Heap([1, 2, 3, 4, 5, 6, 7, 8])
    print h.heap

    # test max_heapify
    h = Heap([16, 4, 10, 14, 7, 9, 3, 2, 8, 1])
    print h.max_heapify(1)

    # test heap_sort
    h = Heap([8, 7, 6, 5, 4, 3, 2, 1])
    print h.heap_sort()

    # test priority queue
    pq = h
    print pq.dequeue()
    print pq.heap
    pq.enqueue(10)
    print pq.heap

Python的heapq模块

Python自带的heapq模块可以轻松实现优先队列操作:
- heapq.heappush(heap, item)
- heapq.heappop(heap)
- heapq.heapify(x),实现线性时间in-place的列表x转化为堆

实现堆排序:

import heapq
def heapsort(iterable):
    h = []
    for value in iterable:
        heappush(h, value)
    return [heappop(h) for i in range(len(h))]

一些优先队列相关的题目

Reference

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值