《算法导论》堆排序和优先队列
来源:互联网 发布:手机逛淘宝费流量吗 编辑:程序博客网 时间:2024/05/23 02:02
堆是一种树形结构,通常基于完全二叉树实现最大堆或最小堆。
堆排序最坏的时间复杂度为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大的数据,海量数据不能全部加载在内存中,可以借助堆来,实现动态的加载数据并产生有序的输出。
具体实现:
取根节点,和堆末节点交换。从后往前遍历堆的节点,取出堆的根节点(堆最大值),和堆的最后一个节点交换,从堆中删除最后一个节点,即取出的最大值(heap_size–)。
重新调整最大堆。从堆末换到根的节点一般都是当前堆里的较小值(不是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 heapqdef heapsort(iterable): h = [] for value in iterable: heappush(h, value) return [heappop(h) for i in range(len(h))]
一些优先队列相关的题目
- HDOJ 1242,某人被关在囚笼里等待朋友解救,问能否解救成功,最少需要多少时间,http://acm.hdu.edu.cn/showproblem.php?pid=1242
- HDOJ 1053,给出一行字符串,求出其原编码需要的编码长度和哈夫曼编码所需的长度,并求其比值,http://acm.hdu.edu.cn/showproblem.php?pid=1053
- POJ 2263,给出各城市间道路的限制载重量,求出从一个城市到另外一个城市的贷车能够运载的最大货物重量,http://acm.pku.edu.cn/JudgeOnline/problem?id=2263
Reference
- 《算法导论》第二版
- 《STL源码剖析》
- Python heapq的官方手册,https://docs.python.org/2/library/heapq.html
- 《算法导论》堆排序和优先队列
- [算法导论读书笔记]堆排序,优先队列
- 算法导论-堆排序+优先队列
- 算法导论:c++堆排序&优先队列
- 算法导论&堆排序&堆实现的优先队列
- 【算法导论】堆排序和优先级队列
- 《算法4》优先队列和堆排序
- 算法导论学习之堆+堆排序+堆构成优先队列
- 堆排序和 优先队列
- 堆排序和优先队列
- 优先队列和堆排序
- 优先队列和堆排序
- 优先队列和堆排序
- 【数据结构】浅谈算法和数据结构:优先队列和堆排序
- 堆排序和优先队列【最大堆】
- 堆排序和优先队列【最小堆】
- 堆、堆排序和优先队列
- 堆、堆排序和优先队列
- 购物商城---购物车,结算
- 链式向前星
- C++默认参数
- MySQL的information_schema介绍
- 线性表(插入/清除数据/快慢指针查找中间数据)
- 《算法导论》堆排序和优先队列
- 常用polyfill代码
- POJ 2699 The Maximum Number of Strong Kings(枚举+最大流)
- Lua + GraphicsMagick安装
- Leetcode——Array 3
- (.vmx)修复方法
- Problem B: STL——管道一
- 《算法导论》线性时间O(n)排序
- 欢迎使用CSDN-markdown编辑器