2.4.5 堆排序

我们可以把任意优先队列变成一种排序方法。将所有元素插入一个查找最小元素的有限队列,然后再重复调用删除最小元素的操作来将他们按顺序删去。用无序数组实现的优先队列这么做相当于进行一次插入排序。用基于堆底优先队列这样做等同于哪种排序?一种全新的排序方法!我们就用堆来实现一种经典的排序算法——堆排序(Heap sort)。

堆排序可以分为两个阶段。在堆的构造阶段中,我们将原始数组重新组织安排进一个堆中;然后在下沉排序阶段,我们从堆中按递减顺序取出所有元素并得到排序结果。为了和我们已经学习过的代码保持一致,我们将使用一个面向大元素的优先队列并重复删除最大元素。为了排序的需要,我们不再将优先队列的具体表示隐藏,并将直接使用swim()和sink()操作。这样我们在排序时就可以将需要排序时就可以将需要排列的数组本身为堆,因此无需任何额外空间。

2.4.5.1 堆的构造

由N个给定的元素构造一个堆有多难?我们当然可以在与NlogN成正比的时间内完成这项任务,只需从左至右遍历数组,用swim()保证扫面指针左侧的所有元素已经是一棵堆有序的完全数即可,就像连续优先队列中插入元素一样。一个更聪明更高效的办法是从右至左用sink() 函数构造子堆。数组的每个位置都已经是一个子堆的根节点了,sink()对于这些子堆也适用。如果一个结点的两个子节点都已经是堆了,那么在该节点上调用sink()可以将它们变成一个堆。这个过程中会递归地建立起堆的只需。开始时我们只需要扫描数组中的一半元素,因为我们可以跳过大小为1的子堆。最后我们在位置1上调用sink()方法,扫描结束。在排序的第一阶段,堆的构造方法和我们的想象有所不同,因为我们的目标是构造一个堆有序的数组并使最大元素位于数组的开头(次大的元素在附近)而非构造函数结束的尾部。

命题R。用下沉操作由N个元素构造堆只需少于2N次比较以及少于N次交换。

证明。观察可知,构造过程中处理的堆都较小。例如,要构造一个127个元素的堆,我们会处理32个大小为3的堆,16个大小为7个堆,8个大小为15的堆,4个大小为31个堆,2个大小为63的堆和一个大小为127的堆,因此(最坏情况下)需要32*1+16*2+8*3+4*4+2*8+1*6= 120次交换(以及两倍的比较)。

 /*************************************************************************
* Compilation: javac Heap.java
* Execution: java Heap < input.txt
* Dependencies: StdOut.java StdIn.java
* Data files: http://algs4.cs.princeton.edu/24pq/tiny.txt
* http://algs4.cs.princeton.edu/24pq/words3.txt
*
* Sorts a sequence of strings from standard input using heapsort.
*
* % more tiny.txt
* S O R T E X A M P L E
*
* % java Heap < tiny.txt
* A E E L M O P R S T X [ one string per line ]
*
* % more words3.txt
* bed bug dad yes zoo ... all bad yet
*
* % java Heap < words3.txt
* all bad bed bug dad ... yes yet zoo [ one string per line ]
*
*************************************************************************/ /**
* The <tt>Heap</tt> class provides a static methods for heapsorting
* an array.
* <p>
* For additional documentation, see <a href="http://algs4.cs.princeton.edu/24pq">Section 2.4</a> of
* <i>Algorithms, 4th Edition</i> by Robert Sedgewick and Kevin Wayne.
*
* @author Robert Sedgewick
* @author Kevin Wayne
*/
public class Heap { // This class should not be instantiated.
private Heap() { } /**
* Rearranges the array in ascending order, using the natural order.
* @param pq the array to be sorted
*/
public static void sort(Comparable[] pq) {
int N = pq.length;
for (int k = N/2; k >= 1; k--)
sink(pq, k, N);
while (N > 1) {
exch(pq, 1, N--);
sink(pq, 1, N);
}
} /***********************************************************************
* Helper functions to restore the heap invariant.
**********************************************************************/ 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;
}
} /***********************************************************************
* Helper functions for comparisons and swaps.
* Indices are "off-by-one" to support 1-based indexing.
**********************************************************************/
private static boolean less(Comparable[] pq, int i, int j) {
return pq[i-1].compareTo(pq[j-1]) < 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;
} // is v < w ?
private static boolean less(Comparable v, Comparable w) {
return (v.compareTo(w) < 0);
} /***********************************************************************
* Check if array is sorted - useful for debugging
***********************************************************************/
private static boolean isSorted(Comparable[] a) {
for (int i = 1; i < a.length; i++)
if (less(a[i], a[i-1])) return false;
return true;
} // print array to standard output
private static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
StdOut.println(a[i]);
}
} /**
* Reads in a sequence of strings from standard input; heapsorts them;
* and prints them to standard output in ascending order.
*/
public static void main(String[] args) {
String[] a = StdIn.readAllStrings();
Heap.sort(a);
show(a);
}
}

这 段代码用sink()方法将a[1]到a[N] 的元素排序(sink()被修改过,以a[]和N作为参数)。for循环构造了堆,然后while循环将最大的元素a[1]和a[N]交换并修复了堆,如 此重复直到堆变空。将exch()和less()的实现中的索引减一即可得到和其他排序算法一致的实现(将a[0]至a[N - 1]排序)。堆排序具体流程示意图如下显示。

2.4.5.2 下沉排序

堆排序的只要工作都是在第二个阶段完成的。这里我们将堆排序中的最大元素删除,然后放入堆缩小后数组中空出的位置。这个过程和选择排序有些类似(按照排序而非升序取出所有元素),但所需的比较要少的多,因为堆提供了一种从未排序部分找到最大的有效方法。

命题S。将N个元素排序,堆排序只少于(2NlgN + 2N)次比较(以及一半次数的交换)。

证明。2N项来自堆的构造(见命题R)。2NlgN项来自每次下沉操作最大可能需要2lgN次比较(见命题P 和命题Q )。

经典的堆排序算法,其发明者是J.W.J.Williams,并由R.W.Floyd 在1964年改进。尽管这段程序中虚幻的任务各不相同(第一段循环构造堆,第二段循环在下沉排序中销毁堆),他们都是基于sink()方法。我们将该实现和优先队列的API 独立开来是为了突出这个排序算法的简洁性(sort() 方法只需8行代码,sink()函数8行),并使其可以嵌入其他代码中。

和以前一样,通过研究可视轨迹(如下图),我们可以深入了解算法的操作。一开始算法的行为似乎杂乱无章,因为随着堆的构造较大的元素都被移动到了数组的开头,但接下来算法的行为看起来就和选择排序一模一样了(除了比较次数更少以外)。

和我们许国的其他算法一样,很多人都研究过许多改进基于堆的有限队列的实现和堆排序的方法。我们这里简要地看看其中之一。

2.4.5.3 先下沉后上浮

大多数在下沉排序期间重新插入堆的元素会被直接加入到堆底,Floyd在1964年观察发现,我们正好可以通过免去检查元素是否到达正确位置来节省时间。在下沉中总是直接提升较大的子节点直至到达堆底,然后再使元素上浮到正确的位置。这个想法几乎可以将次数减少一半——接近了归并排序所需的比较次数(随机数组)。这种方法需要额外的空间,因此在实际应用中只有当比较操作代价较高时才有用(例如,当我们在将字符串或者其他键值较长类型的元素进行排序时)。

堆排序在排序复杂度的研究中有着重要的地位,因为它是我们所知的唯一能够同时最优地利用空间和时间的方法——在最坏的情况下它能保证使用 ~2NlgN次比较和恒定的额外空间。当空间十分紧张的时候(例如在嵌入式系统或低成本的移动设备中)它很流行,因为它只用几行就能实现(甚至机器码也是)很好的性能。但现在系统的许多应用很少使用它,因为它无法利用缓存。数组元素很少和相邻的其他元素进行比较,因此缓存未命中的次数要远远高于大多数比较都在相邻元素间进行的算法,如快速排序、归并排序,甚至是希尔排序。

另一方面,用堆排序的优先队列在现代应用程序中越来越重要,因为它能在插入操作和删除最大元素操作混合的动态场景中保证对数级别的运行时间。


《Algorithms 4th Edition》读书笔记——2.4 优先队列(priority queue)-Ⅶ(延伸:堆排序的实现)的更多相关文章

  1. 《Algorithms 4th Edition》读书笔记——2.4 优先队列(priority queue)-Ⅵ

    · 学后心得体会与部分习题实现 心得体会: 曾经只是了解了优先队列的基本性质,并会调用C++ STL库中的priority_queue以及 java.util.PriorityQueue<E&g ...

  2. 《Algorithms 4th Edition》读书笔记——2.4 优先队列(priority queue)-Ⅴ

    命题Q.对于一个含有N个元素的基于堆叠优先队列,插入元素操作只需要不超过(lgN + 1)次比较,删除最大元素的操作需要不超过2lgN次比较. 证明.由命题P可知,两种操作都需要在根节点和堆底之间移动 ...

  3. 《Algorithms 4th Edition》读书笔记——2.4 优先队列(priority queue)-Ⅰ

    许多应用程序都需要处理有序的元素,但不一定要求他们全部有序,或者是不一定要以此就将他们排序.很多情况下我们会手机一些元素,处理当前键值最大的元素,然后再收集更多的元素,再处理当前键值最大的元素.如此这 ...

  4. 《Algorithms 4th Edition》读书笔记——2.4 优先队列(priority queue)-Ⅳ

    2.4.4 堆的算法 我们用长度为 N + 1的私有数组pq[]来表示一个大小为N的堆,我们不会使用pq[0],堆元素放在pq[1]至pq[N]中.在排序算法中,我们只能通过私有辅助函数less()和 ...

  5. 《Algorithms 4th Edition》读书笔记——2.4 优先队列(priority queue)-Ⅲ

    2.4.3 堆的定义 数据结构二叉堆能够很好地实现优先队列的基本操作.在二叉堆的数组中,每个元素都要保证大于等于另两个特定位置的元素.相应地,这些位置的元素又至少要大于等于数组中的两个元素,以此类推. ...

  6. 《Algorithms 4th Edition》读书笔记——2.4 优先队列(priority queue)-Ⅱ

    2.4.2初级实现 我们知道,基础数据结构是实现优先队列的起点.我们可以是使用有序或无序的数组或链表.在队列较小时,大量使用两种主要操作之一时,或是所操作元素的顺序已知时,它们十分有用.因为这些实现相 ...

  7. C++Primer 4th edition读书笔记-第二章

    1 变量的定义用于为变量分配存储空间,还可以为变量指定初始值.在一个程序中,变量有且只有一个定义.声明用于向程序表明变量的名字和类型.定义也是声明:当定义变量时,我们声明了它的类型和名字.可以通过使用 ...

  8. 《Algorithms 4th Edition》读书笔记——3.1 符号表(Elementary Symbol Tables)-Ⅳ

    3.1.4 无序链表中的顺序查找 符号表中使用的数据结构的一个简单选择是链表,每个结点存储一个键值对,如以下代码所示.get()的实现即为遍历链表,用equals()方法比较需被查找的键和每个节点中的 ...

  9. 《C++ Primer 4th》读书笔记 序

    注:本系列读书笔记是博主写作于两三年前的,所以是基于<C++ Primer>第四版的,目前该书已更新至第五版,第五版是基于C++11标准的,貌似更新挺多的.博主今年应届硕士毕业,如若过阵子 ...

随机推荐

  1. UIView的setNeedsLayout, layoutIfNeeded 和 layoutSubviews 方法之间的关系解释

    转自:http://blog.csdn.net/meegomeego/article/details/39890385 layoutSubviews总结 ios layout机制相关方法 - (CGS ...

  2. Sass函数--颜色函数--Opacity函数

    Opacity函数简介 在 CSS 中除了可以使用 rgba.hsla 和 transform 来控制颜色透明度之外,还可以使用 opacity 来控制,只不过前两者只是针对颜色上的透明通道做处理,而 ...

  3. C# XML 根级别上的数据无效

    XmlDocument加载xml方法 XmlDocument doc = new XmlDocument(); //加载xml 字符串 doc.LoadXml(_Store); //加载xml文件 d ...

  4. MySQL 数据库入门操作

    启动mysqld:在命令行启动mysql时,如不加"--console",启动.关闭信息不在界面中显示,而是记录在安装目录下的data目录里,文件名一般是hostname.err, ...

  5. gitweb随记

    1.安装gitweb,命令安装即可 apt-get install gitweb 2.clone cgi $ git clone git://git.kernel.org/pub/scm/git/gi ...

  6. OpenCV——ANN神经网络

    ANN-- Artificial Neural Networks 人工神经网络 //定义人工神经网络 CvANN_MLP bp; // Set up BPNetwork's parameters Cv ...

  7. C++ 虚函数详解

    C++ 虚函数详解 这篇文章主要是转载的http://blog.csdn.net/haoel/article/details/1948051这篇文章,其中又加入了自己的理解和难点以及疑问的解决过程,对 ...

  8. 最常用的CSS技巧收集笔记

    1.重置浏览器的字体大小  重置浏览器的默认值 ,然后重设浏览器的字体大小你可以使用雅虎的用户界面重置的CSS方案 ,如果你不想下载9MB的文件,代码如下: body,div,dl,dt,dd,ul, ...

  9. file_get_contents 获取不了网页内容

    服务器在做验签的过程中,经常需要向渠道服务器获取某个用户的信息.一般有两种方法,curl和file_get_contents. 一般情况下,像这样用,不会有问题. public function Oa ...

  10. ubuntu下安装postgres

    PostgreSQL 是一款强大的,开源的,对象关系型数据库系统.它支持所有的主流操作系统,包括 Linux.Unix(AIX.BSD.HP-UX,SGI IRIX.Mac OS.Solaris.Tr ...