您的位置:首页 > 编程语言

【内部排序】七:堆排序(Heap Sort)详解与代码(超详细注释版)

2015-04-22 22:03 309 查看
堆排序是选择排序的一种,每一趟从待排序的记录中选出关键字最小的记录,顺序放在有序的子表中,直到全部记录排序完毕。

关于堆排序的讲解,兰亭风雨/article/1337333.html里讲解的非常好,推荐大家看看。

我这里做个总结:
二叉堆: 二叉堆其实是一棵有着特殊性质的完全二叉树,父节点的值总是大于等于(或小于等于)其左右孩子的值;每个节点的左右子树都是一棵这样的二叉堆。
大根堆和小根堆:如果一个二叉堆的父节点的值总是大于其左右孩子的值,那么该二叉堆为大根堆,反之为小根堆。

堆排序: 输出堆顶元素后,将剩余的元素再调整为二叉堆,继而再次输出堆顶元素,再将剩余的元素调整为二叉堆,反复执行该过程,这样便可输出一个有序序列,这个过程叫做堆排序。

实现堆排序的三个步骤如下:
1、如何将一个无序序列建成一个二叉堆(构造初始堆);
叶子节点可以认为是一个堆(因为堆的定义中并没有对左右孩子间的关系有任何要求,所以可以将这几个叶子节点看做是一个堆),然后从第一个非叶子节点向前直到第一个节点,插入堆中,构成新堆。可利用下面的调整堆实现。

//把数组建成为大根堆,循环建立初始堆(从第一个非叶子节点向前直到第一个节点)
	//第一个非叶子节点的位置序号为n/2-1
	for (i=n/2-1;i>=0;i--)
		HeapSift(a,i,n-1);


2、在去掉堆顶元素后,如何将剩余的元素调整为一个二叉堆(调整堆);

//堆排序——调整堆
/*
arr[low+1...high]满足大根堆的定义,
将arr[low]加入到最大堆arr[low+1...high]中,
调整arr[low]的位置,使arr[low...high]也成为大根堆
注:由于数组从0开始计算序号,也就是二叉堆的根节点序号为0,
因此序号为i的左右子节点的序号分别为2i+1和2i+2
*/
void HeapSift(int a[],int low,int high)
{
	int temp=a[low];		//保存堆中第一个节点(要调整)
	int i=2*low+1;			//a[i]是a[low]的左孩子,i为该节点的左孩子在数组中的位置序号
	while(i<high)			
	{
		//比较左右孩子,找出左右孩子中最大的那个
		if (i+1<=high&&a[i+1]>a[i])
			i++;
		if (a[i]>temp)		//如果子节点大于父节点
		{
			a[low]=a[i];	//把最大的子节点赋值给父节点(比较后不用交换,子节点的值还是原来的值(可以理解为挖空))
			low=i;			//修改low(把i位置作为父节点),以便继续向下调整
			i=2*low+1;		//修改i(令i为low位置节点的左孩子序号),以便继续向下调整
		} 
		else				//如果符合堆的定义,则不用调整位置
			break;
	}
	a[low]=temp;			//把堆中第一个节点放入最终位置(最后挖空的位置),调整完毕。
}


3、最后完成堆排序。
堆顶元素(a[0])和最后一个元素(a[i])交换位置,最大元素归位。

//进行堆排序
	for (i=n-1;i>0;i--)			//从后往前,保存最大的数
	{
		//堆顶元素(a[0])和最后一个元素(a[i])交换位置,
		//这样最后的一个位置保存的是最大的数,最大元素归位
		//每次循环依次将次大的数值在放进其前面一个位置,
		//这样得到的顺序就是从小到大
		int temp =a[i];		
		a[i]=a[0];				
		a[0]=temp;				
		HeapSift(a,0,i-1);		//堆顶元素(a[0])保存最后一个位置的元素,导致不一定为堆,所以重新调整
	}


算法分析

堆排序的最坏时间复杂度为O(nlgn)。堆排序的平均性能较接近于最坏性能。

 由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。

 堆排序是就地排序,辅助空间为O(1),

 它是不稳定的排序方法。

最后给出超详细注释版的完整源码:

#include <iostream>
using namespace std;

//堆排序——调整堆 /* arr[low+1...high]满足大根堆的定义, 将arr[low]加入到最大堆arr[low+1...high]中, 调整arr[low]的位置,使arr[low...high]也成为大根堆 注:由于数组从0开始计算序号,也就是二叉堆的根节点序号为0, 因此序号为i的左右子节点的序号分别为2i+1和2i+2 */ void HeapSift(int a[],int low,int high) { int temp=a[low]; //保存堆中第一个节点(要调整) int i=2*low+1; //a[i]是a[low]的左孩子,i为该节点的左孩子在数组中的位置序号 while(i<high) { //比较左右孩子,找出左右孩子中最大的那个 if (i+1<=high&&a[i+1]>a[i]) i++; if (a[i]>temp) //如果子节点大于父节点 { a[low]=a[i]; //把最大的子节点赋值给父节点(比较后不用交换,子节点的值还是原来的值(可以理解为挖空)) low=i; //修改low(把i位置作为父节点),以便继续向下调整 i=2*low+1; //修改i(令i为low位置节点的左孩子序号),以便继续向下调整 } else //如果符合堆的定义,则不用调整位置 break; } a[low]=temp; //把堆中第一个节点放入最终位置(最后挖空的位置),调整完毕。 }
/*
堆排序(从小到大)
需要建立大根堆
*/
void Heap_Sort(int a[],int n)
{
int i;
//把数组建成为大根堆,循环建立初始堆(从第一个非叶子节点向前直到第一个节点)
//第一个非叶子节点的位置序号为len/2-1
for (i=n/2-1;i>=0;i--)
HeapSift(a,i,n-1);
//进行堆排序 for (i=n-1;i>0;i--) //从后往前,保存最大的数 { //堆顶元素(a[0])和最后一个元素(a[i])交换位置, //这样最后的一个位置保存的是最大的数,最大元素归位 //每次循环依次将次大的数值在放进其前面一个位置, //这样得到的顺序就是从小到大 int temp =a[i]; a[i]=a[0]; a[0]=temp; HeapSift(a,0,i-1); //堆顶元素(a[0])保存最后一个位置的元素,导致不一定为堆,所以重新调整 }
}
int main()
{
int a[8] = {3,1,5,7,2,4,9,6};
print(a,8);
//straight_insertion_sort4(a,8);
//binary_insertion_sort(a,8);
//shell_sort(a,8);
//Bubble_Sort(a,8);
//select_sort(a,8);
Heap_Sort(a,8);
print(a,8);
system("pause");
return 0;
}


print()没有实现,很简单如下:

void print(int a[],int n)
{
	for (int j=0;j<n;j++)
	{
		cout<<a[j]<<" ";
	}
	cout<<endl;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: