这一章开始我们介绍堆排序 Heap Sort。
首先,我们应该认识到这样一个问题:O(nlogn) 比 O(n^2) 快多少?结论:快很多。理解这一步是我们理解算法重要性的基础。
堆的非常典型的应用
堆的一个典型的应用是优先队列(Priority Queue)。
普通队列 | 优先队列 |
---|---|
先进先出,后进后出。由进入队列的时间顺序决定。 | 出队顺序与入队顺序无关,只与队列中元素的优先级有关,优先级最高的元素最先出队。 |
优先队列在生活中的例子:
- 医院看病
- 操作系统:选择优先级最高的任务执行。特别注意:理解“动态执行”这个概念。
- 上网:服务端依次回应客户端的请求:通常也是使用优先队列。
- 人工智能(游戏)领域的典型应用。入队、出队的操作是很频繁的。
优先队列主要的使用场景是处理动态的情况。但是在静态问题的求解上堆也有重要应用。下面就是一个处理静态情况的例子。
问题:从 1000000 元素中选出前 100 名。
问题抽象:在 N 个元素中选出前 M 个元素。
使用排序的时间复杂度为:O(NlogN),使用优先队列的时间复杂度为:O(NlogM)
在这一章的末尾我们将要解决这样的问题。
优先队列的主要操作
优先队列的主要操作:
- 入队
- 出队(取出优先级最高的那个元素)
优先队列的一个重要特点是:出队的时候总是取出优先级最高的那个元素。
三种数据结构对于实现优先队列的时间复杂度的比较
入队操作 | 出队操作 |
---|---|
普通数组 | O(1) |
顺序数组 | 因为入队的时候,要维护数组的有序性,所以时间复杂度为:O(n) |
堆 | O(lgn) |
其中:lgn 表示以 2 为底的 n 的对数。
于是,我们伟大的计算机科学家平衡了入队和出队这两个操作的时间复杂度,这种数据结构就是堆。
对于总共 N 个请求:使用普通数组或者顺序数组,最差的情况是 O(n^2)。
使用堆:O(logn),事实上,时间复杂度是 O(n^2) 与 O(logn) 的差异巨大的。
优先队列的实现可以通过堆(最大堆和最小堆)来实现,下面就是一个堆的例子。
上面就是一个最大堆,并且每一个子树都维持了保持了最大堆的定义(儿子节点不大于父节点就可以了)。