堆排序算法设计与分析

堆排序(HeapSort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。堆分为大根堆和小根堆,是完全二叉树。大根堆要求父结点的值大于或等于子结点的值,小根堆相反。根据大根堆的性质,我们可以知道最大值一定在堆顶,即根结点,利用这一点我们可以将数组建成大根堆。这里我以大根堆为例,小根堆类似。

大根堆的主要思想是:先将数组建为大根堆(建堆过程后面介绍),此为初始堆。此时我们知道堆顶元素为最大值,即数组第一个元素为最大值,我们将数组第一个元素和最后一个交换,此时数组最后一个元素为最大值,然后我们对第一个元素在去除最后一个元素的堆中进行更新,保证第一个元素在当前堆中为最大值,再次与此时堆中最后一个元素交换,再次去除最后一个元素更新堆,依次类推,直到堆中只剩一个元素。

大根堆排序算法的基本操作:
①建堆,建堆是不断调整堆的过程,从(len/2-1)处,即最后一个非叶结点开始调整,一直到第一个节点,此处len是堆中元素的个数。建堆的过程是线性的过程,从len/2-1到0处一直调用调整堆的过程,相当于o(h1)+o(h2)…+o(h(len/2-1)) 其中h表示节点的深度,len/2-1表示节点的个数,这是一个求和的过程,结果是线性的O(n)。
②调整堆:调整堆在构建堆的过程中会用到,而且在堆排序过程中也会用到。利用的思想是比较节点i和它的孩子节点left(i),right(i),选出三者最大(或者最小)者,如果最大(小)值不是节点i而是它的一个孩子节点,那边交互节点i和该节点,然后再调用调整堆过程,这是一个递归的过程。调整堆的过程时间复杂度与堆的深度有关系,是lgn的操作,因为是沿着深度方向进行调整的。
③堆排序:堆排序是利用上面的两个过程来进行的。首先是根据元素构建堆。然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有节点都取出。堆排序过程的时间复杂度是O(nlgn)。因为建堆的时间复杂度是O(n)(调用一次);调整堆的时间复杂度是lgn,调用了n-1次,所以堆排序的时间复杂度是O(nlgn)。

可以参考下图理解:

《堆排序算法设计与分析》《堆排序算法设计与分析》

《堆排序算法设计与分析》《堆排序算法设计与分析》

《堆排序算法设计与分析》《堆排序算法设计与分析》

《堆排序算法设计与分析》

《堆排序算法设计与分析》


下面是代码:

<span style="font-size:14px;">//本函数功能是:根据数组arr构建大根堆
//arr是待调整的堆数组,index是待调整的数组元素的位置,length是数组的长度
void HeapAdjust(int* arr, int index, int length)
{
	int nChild;//子结点
	int temp;
	for (; 2 * index + 1 < length; index = nChild)
	{
		//子结点的位置=2*(父结点位置)+1
		nChild = 2 * index + 1;
		//得到子节点中较大的结点
		if (nChild<length - 1 && arr[nChild + 1]>arr[nChild])
			nChild++;
		//如果较大的子结点大于父结点那么把较大的子结点往上移动,与父结点交换
		if (arr[index] < arr[nChild])
		{
			temp = arr[index];
			arr[index] = arr[nChild];
			arr[nChild] = temp;
		}
		else
			break;//如果子结点小于父结点则退出循环
	}
}

//堆排序
void HeapSort(int* arr, int length)
{
	int i;
	//构建大根堆,构建完后第一个元素是序列的最大元素
	for (i = length / 2 - 1; i >= 0; --i)//(length/2-1)是最后一个非叶结点
		HeapAdjust(arr, i, length);
	//从最后一个元素开始对序列进行调整,不断缩小范围直到第一个元素
	for (i = length - 1; i > 0; --i)
	{
		//把第一个元素(根元素,也就是最大的元素)和当前最后一个交换
		arr[i] = arr[0] ^ arr[i];
		arr[0] = arr[0] ^ arr[i];
		arr[i] = arr[0] ^ arr[i];
		//不断缩小调整堆的范围,每一次调整完毕后保证第一个元素是当前序列的最大值
		HeapAdjust(arr, 0, i);
	}
}</span>


堆排序是就地排序,辅助空间为o(1)。它是不稳定的排序算法。

从平均时间性能而言,快速排序最佳,其所需时间最省,但快速排序在最坏情况下的时间性能不如堆排序和归并排序。而后两者比较的结果是,在n较大时,归并排序所需时间较堆排序省,但它所需的辅助存储量最多。


我测试了一个一亿的数组,快速排序用了28.792393秒,而堆排序用了102.245783秒。


通俗点讲,堆排序就是分为建堆和更新堆两个步骤。
 堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征, 使得在当前无序区中选取最大(或最小)关键字的记录变得简单。   
1)用大根堆排序的基本思想    ① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区    ②
再将关键字最大的记录R[1](即堆顶)和无序区的最后一个 记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],
且满足R[1..n-1].keys≤R[n].key    ③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。
然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,
由此得到新的无序区R[1..n-2]和有序区R[n-1..n],
且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。   直到无序区只有一个元素为止。   
2)大根堆排序算法的基本操作:    ① 初始化操作:将R[1..n]构造为初始堆;    ②
每一趟排序的基本操作:将当前无序区的堆顶记录R[1]和该区间的最后一个记录交换, 然后将新的无序区调整为堆(亦称重建堆)。

更多排序及比较请看我的另一篇博客”排序算法及并行分析”:http://blog.csdn.net/secyb/article/details/51319391

    原文作者:排序算法
    原文地址: https://blog.csdn.net/secyb/article/details/51376192
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞