排序算法总结之堆排序

一,堆排序介绍

堆是一个优先级队列,对于大顶堆而言,堆顶元素的权值最大。将 待排序的数组 建堆,然后不断地删除堆顶元素,就实现了排序。关于堆,参考:数据结构--堆的实现之深入分析

下面的堆排序算法将数组中的元素从小到大排序,用大顶堆来实现。

二,堆排序算法分析

现给定了一维数组,需要将数组中的元素使用堆排序。首先,得创建一个堆,可以在这个给定的一维数组上建堆。 对N个元素 建堆的时间复杂度为O(N)

堆排序具体的细节实现有两种方式:一种方式是将堆顶元素删除后,放到一个辅助数组中,然后进行堆调整使之成为一个新。继续删除堆顶元素,直至将堆中所有的元素都出堆,此时排序完成。这种方式需要一个额外的辅助空间O(N)

另一种方式是:将每次删除的堆顶元素放到数组的末尾。因为,对于堆的基本操作 delMin/delMax 而言(delMin针对的是小顶堆,delMax针对的是大顶堆,原理一样)是将堆中的最后一个元素替换堆顶元素,然后向下进行堆调整。因此,可以利用这个特点将每次删除的堆顶元素保存在数组末尾,当所有的元素都出堆后,数组就排好序了。这种方式不需要额外的辅助空间,空间复杂度为O(1)

三,堆排序算法实现

 1 public class HeapSort {
 2
 3     public static <T extends Comparable<? super T>> void heapSort(T[] arr){
 4         //build heap
 5         for(int i = arr.length/2 - 1; i >= 0; i--)
 6             percDown(arr, i, arr.length);
 7
 8
 9         for(int i = arr.length - 1; i >= 0; i--)
10         {
11             swapReference(arr, 0, i);//delete Max
12
13             percDown(arr, 0, i);// 从根开始向下堆调整
14         }
15     }
16
17     private static <T extends Comparable<? super T>> void swapReference(T[] arr, int from, int to){
18         T tmp;
19         tmp = arr[from];
20         arr[from] = arr[to];
21         arr[to] = tmp;
22     }
23
24     //求解 i 的左孩子
25     private static int leftChild(int i){
26         return 2*i + 1;
27     }
28
29     /**
30      *
31      * @param arr 存储堆的一维数组
32      * @param i 从 i 位置开始进行向下堆调整
33      * @param n 堆中元素的个数(不是数组的长度)
34      */
35     private static <T extends Comparable<? super T>> void percDown(T[] arr, int i, int n){
36         int child;
37         T tmp;//保存当前待调整的结点,当找到了合适的位置后,需要将之放入到合适位置,以保持堆序性质
38
39         for(tmp = arr[i];  leftChild(i) < n; i = child)
40         {
41             child = leftChild(i);
42             if(child != n-1 && arr[child].compareTo(arr[child+1]) < 0)
43                 child++;//右孩子更大
44             if(tmp.compareTo(arr[child]) < 0)
45                 arr[i] = arr[child];//父节点下移
46             else
47                 break;//父节点比左右孩子都大时,不需要再向下移动了
48         }
49         arr[i] = tmp;//将节点放入合适的位置
50     }
51
52     //for test purpose
53     public static void main(String[] args) {
54         Integer[] arr = {31,41,59,26,53,58,97};
55         heapSort(arr);
56         for (Integer i : arr) {
57             System.out.print(i + " ");
58         }
59     }
60 }

有几个细节地方解释一下:

①在第3行的heapSort方法中,第5-6行是建堆操作,因为数组中的元素是从下标0开始存储的,故最后一个非叶子结点的下标为:arr.length/2 - 1

②第9-14行是进行堆排序的操作。swapReference方法相当于删除堆顶元素,因为它把堆顶元素交换到数组的末尾去了,此时堆顶元素不再是最大值(大顶堆)。删除了堆顶元素之后,就要进行堆调整以保持堆序性质,故percDown方法 完成向下进行堆调整的功能。

③在堆调整的过程中,需要求解某个结点的左 右孩子结点的位置。故有一个leftChild方法用来求解左孩子的位置(注意元素是从数组下标0开始存储的)

④percDown方法实现向下的堆调整功能。第37行  tmp 变量 保存当前待调整的结点,当找到了合适的位置后,需要将之放入到合适位置,以保持堆序性质。对于建堆而言,待调整的结点是从 非叶结点 开始,直至根的那些结点。对于删除堆顶元素而言,则总是从堆顶元素起开始调整(待调整的结点是根)

⑤第39行的for循环实现得非常巧妙,首先tmp保存当前待调整的结点 arr[i],然后判断 arr[i] 是否有左孩子,如果有左孩子的话,又在第42行的if语句中判断它是否还有右孩子(child != n-1),然后左右孩子进行比较,child记录下权值大的那个孩子。

⑥第44-45行的if语句完成的功能是:将权值大的孩子与父结点比较,如果父结点的权值小,则需要将那个较大的孩子上移到父结点的位置(也相当于父结点下移到孩子的位置)

如果父结点的权值大,已经找到了合适的位置了。说明不需要再进行堆调整了,执行else break;

⑦第49行,就待调整的结点入到到合适的位置i处。整个过程并没有用交换操作,而是用的是赋值操作来隐式地实现了交换操作完成的功能,这是一个优化。

四,堆排序算法复杂度分析

对N个元素建堆的时间复杂度为O(N),删除堆顶元素的时间复杂度为O(logN),尽管随着元素的不断删除,堆的调度越来越小,但是总的而言,删除堆所有元素的时间复杂度为O(NlogN)

故堆排序的时间复杂度为O(NlogN),空间复杂度为O(1)

其实,堆排序是一个非常稳定的算法,最坏和平均情况下的时间复杂度都为O(NlogN)

此外,对于堆排序而言,数据的初始顺序对它的复杂度没有影响。不管数组初始时就是有序的还是逆序的,它都会先建堆,变成了堆序的性质。

五,参考资料

排序算法总结之插入排序

排序算法总结之快速排序

排序算法总结之归并排序

各种排序算法的总结

时间: 2024-10-15 10:31:02

排序算法总结之堆排序的相关文章

(高效率排序算法三)堆排序

一.堆的介绍         动态效果图         堆有如下特点的二叉树: 1.他是完全的二叉树.也就是说,除了树的最后一层布需要时满的,其他的每一层从左到右都是满的.(如下图的完全二叉树跟不完全二叉树) 2.它常常用一个数组在实现.(如下图显示了堆它与数组之间的关系.堆在存储器中的表示是数组:堆只是概念上的表示.注意树是完全二叉树,并且所有的节点满足堆的条件) 3.堆中的每一个节点都满足堆的条件,也就是说每一个节点的值都大于或者等于这个节点的子节点的值(如上图) 二,堆的移除 1.只能移

排序算法学习之堆排序

一.堆与堆排序的产生及定义 在简单选择排序中,每次从n个元素中比较n-1次选取最小的元素,这很好理解,但是前面比较过的数据在之后还要重新比较,这将花费大量的运算时间.堆排序算法就很好的解决了这个问题,堆排序在每次选择到最小记录的同时会根据比较结果对其他数据进行调整,堆排序的时间复杂度为O(NlogN). 堆通常是指二叉堆,即堆是一颗完全二叉树,同时其满足一定性质:每个节点的值大于等于其左右孩子的值(大顶堆),或者每个节点的值小于等于其左右孩子的值(小顶堆).堆在本质上是一个数组,根节点即为a[0

排序算法(三)之堆排序

预备知识 堆排序 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序.首先简单了解下堆结构. 堆 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆:或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆.如下图: 同时,我们对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子 该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是: 大顶

排序算法三:堆排序(Heapsort)

堆排序(Heapsort)是一种利用数据结构中的堆进行排序的算法,分为构建初始堆,减小堆的元素个数,调整堆共3步. (一)算法实现 1 protected void sort(int[] toSort) { 2 buildHeap(toSort); 3 for (int i = toSort.length - 1; i > 0; i--) { 4 CommonUtils.swap(toSort, 0, i); 5 adjustHeap(toSort, 0, i); 6 } 7 } 8 9 /**

Java排序算法(五):堆排序

[算法说明] 堆排序是对简单选择排序的改进 简单选择排序是从n个记录中找出一个最小的记录,需要比较n-1次.但是这样的操作并没有把每一趟的比较结果保存下来,在后一趟的比较中,有许多比较在前一趟已经做过了,但由于前一趟排序时未保存这些比较结果,所以后一趟排序时又重复执行了这些比较操作,因而记录的比较次数较多. 堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆:或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆. [算法思想] 将待排序的序列构造成一个大顶

深入浅出排序算法之-堆排序

#include <iostream> void Swap(int &a,int &b){ int temp; temp = a; a = b; b = temp; } // 维护最小堆 void AdjustMinHeap(int *a,int pos,int len){ int temp,child; for (temp = a[pos]; 2*pos + 1 <= len; pos = child) {        // 不断下移父结点,直到结束 // 左孩子(因

深入浅出理解排序算法之-堆排序

#include <iostream> void Swap(int &a,int &b){ int temp; temp = a; a = b; b = temp; } // 维护最小堆 void AdjustMinHeap(int *a,int pos,int len){ int temp,child; for (temp = a[pos];2*pos +1 <= len; pos = child) {       //不断下移父结点,直到结束 // 左孩子(由于第一个

排序算法(三)堆排序及有界堆排序Java实现及分析

1.堆排序基数排序适用于大小有界的东西,除了他之外,还有一种你可能遇到的其它专用排序算法:有界堆排序.如果你在处理非常大的数据集,你想要得到前 10 个或者前k个元素,其中k远小于n,它是很有用的. 例如,假设你正在监视一 个Web 服务,它每天处理十亿次事务.在每一天结束时,你要汇报最大的k个事务(或最慢的,或者其它最 xx 的).一个选项是存储所有事务,在一天结束时对它们进行排序,然后选择最大的k个.需要的时间与nlogn成正比,这非常慢,因为我们可能无法将十亿次交易记录在单个程序的内存中.

各种排序算法的复杂度和稳定性

名称 数据对象 稳定性 时间复杂度 ?? 空间复杂度 描述 ?? ?? ?? 平均 最坏 ?? ?? 冒泡排序 数组 ?? (无序区,有序区).从无序区通过交换找出最大元素放到有序区前端. 选择排序 数组 ?? (有序区,无序区).在无序区里找一个最小的元素跟在有序区的后面.对数组:比较得多,换得少. ?? 链表 ?? ?? ?? ?? 插入排序 数组.链表 ?? (有序区,无序区).把无序区的第一个元素插入到有序区的合适的位置.对数组:比较得少,换得多. 堆排序 数组 ?? (最大堆,有序区)