堆排序比较和交换次数比快速排序多,所以平均而言比快速排序慢,也就是常数因子比快速排序大,如果你需要的是“排序”,那么绝大多数场合都应该用快速排序而不是其它的O(nlogn)算法。 但有时候你要的不是“排序”,而是另外一些与排序相关的东西,比如最大/小的元素,topK之类,这时候堆排序的优势就出来了。用堆排序可以在N个元素中找到top K,时间复杂度是O(N log K),空间复杂的是O(K),而快速排序的空间复杂度是O(N),也就是说,如果你要在很多元素中找很少几个top K的元素,或者在一个巨大的数据流里找到top K,快速排序是不合适的,堆排序更省地方。 另外一个适合用heap的场合是优先队列,需要在一组不停更新的数据中不停地找最大/小元素,快速排序也不合适。 此外merge sort之类算法虽说也是O(nlogn),但一般都只在一些很特殊的场合才会用,比如N-way merge,可以把N个已经排好序的数据流合并成一个排好序的数据流,当然这个算法其实严格说并不能算是merge sort,只是用了其中的几个步骤,不过思路是一样的。 基于交换的排序常用的就这么几种(什么冒泡选择之类的你可以无视了),其它的不基于交换的排序比如radix sort、bucket sort之类由于应用场合比较特殊,一般很少用到。 |
构造堆
在构造有序堆时,我们开始只需要扫描一半的元素(n/2-1 ~ 0)即可,为什么?
因为(n/2-1)~0的节点才有子节点,如图1,n=8,(n/2-1) = 3 即3 2 1 0这个四个节点才有子节点
(图1:初始状态)
所以代码4~6行for循环的作用就是将3 2 1 0这四个节点从下到上,从右到左的与它自己的子节点比较并调整最终形成大顶堆,过程如下:
第一次for循环将节点3和它的子节点7 8的元素
进行比较,最大者作为父节点(即元素60作为父节点)
【红色表示交换后的状态】
第二次
for循环将节点2和它的子节点5 6的元素进行比较,最大者为父节点(元素80作为父节点)
第三次
for循环将节点1和它的子节点3 4的元素进行比较,最大者为父节点(元素70作为父节点)
第四次
for循环将节点0和它的子节点1 2的元素进行比较,最大者为父节点(元素80作为父节点)
(注意这里,元素20和元素80交换后,20所在的节点还有子节点,所以还要再和它的子节点5 6的元素进行比较,
这就是28行代码 i = j 的原因)
至此有序堆已经构造好了!如下图:
调整堆
下面进行while循环
(1)堆顶元素80和尾40交换后-->调整堆
(2)堆顶元素70和尾30交换后-->调整堆
(3)堆顶元素60尾元素20交换后-->调整堆
(4)其他依次类推,最终已排好序的元素如下: