1、引言
简单选择排序算法是通过比较,确定最终的位置。假设未排序的元素个数为N,则遍历一趟,需要比较N-1次,再遍历下一趟时,需比较N-2次。但是,第二次的比较是完全独立的,没有利用第一次比较的信息,因为第一次比较时也没有把比较信息保留下来。那么能否找到一种方法,可以将本趟比较信息记录下来,以供下一次求最值时使用,从而达到减少比较次数的目的呢?
下面介绍的堆排序就是一种利用堆的性质来进行的选择排序。堆排序正是利用一位数组可表示完全二叉树,从而借助完全二叉树的性质来保存比较信息。
这里我们只介绍大顶堆,大顶堆满足以下基本性质:Key[i]>=Key[2i+1]&&key>=key[2i+2],根据大顶堆的性质易知堆顶的关键字肯定是所有关键字中最大的。
2、堆排序的基本思想(大顶堆)
1) 先将初始排列关键字序列(R1,R2…,Rn-1,Rn)构成大顶堆,此堆为初始的无序区.(这里是从最后一个非叶结点向前进行赛选)
2)将堆顶元素R1与最后一个元素Rn交换,此时得到新的无序区(R1,R2…,Rn-1)和新的有序区(Rn),并且Rn大于无序区所有数,此后还有n-1个数;
3)由于交换后新的堆顶R1可能违反堆的性质,因此需要对当前无序区(R1,R2…,Rn-1)调整为新堆(将堆顶元素向下调整使其保持大顶堆的性质,输出堆顶元素),此后还剩余n-2个数;
4)重读以上算法,直到堆中仅剩一个元素为止.
3、基本算法如下:
void heapAjust(int arr[], int s, int len)
{
int temp = arr[s]; //暂存最大结点
int i;
for (i = 2 * s; i <= len; i *= 2) //从s的左孩子结点开始比较
{
if ((i < len) && (arr[i] < arr[i+1]))
i++;
if(arr[i] <= temp)
break;
arr[s] = arr[i]; //将a[i]调整到双亲结点上
s = i; //修改s的值,以便继续向下赛选
}
arr[s] = temp;
}
//堆排序
void heapSort(int arr[],int len)
{
int i;
for (i = len / 2; i > 0; i--)
heapAjust(arr,i,len); //建堆,从最后一个非叶结点反复调整的过程
for (i = len; i > 1; i--)
{
swap(arr[1],arr[i]);
heapAjust(arr,1,i - 1); //每次循环之后所需处理的元素个数减1
}
}
4、堆排序小结
堆排序的最坏时间复杂度为O(N*logN),其平均性能较接近于最坏性能。由于初始建堆所需比较的次数较多,所以堆排序不适合记录数较少的文件,其空间复杂度是O(1),它是一种不稳定的排序算法.