定义:一种支持删除最大元素和插入元素的数据结构。
经典实现:基于二叉堆数据结构。
2.4.1 API
1. 只要我们能够高效地实现insert()和delMin(),下面的优先队列用例中调用了MinPQ的TopM就能使用优先队列解决这个问题。
2.4.2 初级实现
1. 数组实现(无序):修改pop(),先交换再删除,相当于选择排序(个人认为)。 -> 惰性方法
2. 数组实现(有序):修改insert(),每次插入后保证最大值在栈的顶部。 -> 积极方法
3. 链表表示法:用基于链表的下压栈的代码作为基础,选择上面二者之一实现。
4. 优先队列的各种实现在最坏情况下运行时间的增长数量级
数据结构 | 插入元素 | 删除最大元素 |
有序数组 | N | 1 |
无序数组 | 1 | N |
堆 | logN | logN |
理想情况 | 1 | 1 |
2.4.3 堆的定义
1. 堆有序:一棵二叉树的每个结点都大于等于它的两个子结点。
2. 根节点是堆有序的二叉树中的最大结点。
3. 二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级储存(不使用数组的第一个位置)。
4.* 在一个堆中,位置k的结点的父结点的位置为⌊k / 2⌋,而它的两个子结点的位置则分别为2k和2k + 1。
5. 一棵大小为N的完全二叉树的高度为⌊lgN⌋。
2.4.4 堆的算法
1. 理解上浮(swim)和下沉(sink)。
2. 插入元素。
我们将新元素加到数组末尾,增加堆的大小并让这个新元素上浮到合适的位置。
3. 删除最大元素。
我们从数组顶端删去最大的元素并将数组的最后一个元素放到顶端,减小堆的大小并让这个元素下沉到合适的位置。
4. 对于一个含有N个元素的基于堆的优先队列,插入元素操作只需不超过(lgN + 1)次比较,删除最大元素的操作需要不超过2lgN次比较。
2.4.5 堆排序
public class Heap { public static void sort(Comparable[] a) { int N = a.length; for (int k = N / 2; k >= 1; k--) sink(a, k, N); while (N > 1) { exch(a, 1, N--);y sink(a, 1, N); } } private static boolean less(Comparable[] pq, int i, int j) { return pq[i-1].compareTo(pq[j-1]) < 0; } private static boolean less(Comparable v, Comparable w) { return v.compareTo(w) < 0; } private static void exch(Object[] pq, int i, int j) { Object swap = pq[i-1]; pq[i-1] = pq[j-1]; pq[j-1] = swap; } private static void sink(Comparable[] pq, int k, int n) { while (2*k <= n) { int j = 2*k; if (j < n && less(pq, j, j+1)) j++; if (!less(pq, k, j)) break; exch(pq, k, j); k = j; } } public static boolean isSorted(Comparable[] a) { // 测试数组元素是否有序 for (int i = 1; i < a.length; i++) if (less(a[i], a[i - 1])) return false; return true; } private static void show(Comparable[] a) { // 在单行中打印数组 for (int i = 0; i < a.length; i++) StdOut.print(a[i] + " "); StdOut.println(); } public static void main(String[] args) { // 从标准输入读取字符串,将它们排序并输出 String[] a = In.readStrings(); sort(a); assert isSorted(a); show(a); } }
1. 用下沉操作由N个元素构造堆只需少于2N次比较以及少于N次交换。
2. 堆排序在排序复杂性的研究中有着重要的地位,因为它是我们所知的唯一能够同时最优地利用空间和时间的方法--在最坏的情况下它也能保证使用~2NlgN次比较和恒定的额外空间。当空间十分紧张的时候(例如在嵌入式系统或低成本的移动设备中)它很流行,因为他只用几行就能实现(甚至机器码也是)较好的性能。
3. 但现代系统的许多应用很少使用它,因为它无法利用缓存。数组元素很少和相邻的其他元素进行比较,因此缓存未命中的次数要远远高于大多数比较都在相邻元素间进行的算法,如快速排序、归并排序,甚至是希尔排序。