堆排序是一种具有合并排序和插入排序共同优点的排序方法。它的时间复杂度为O(nlgn),它也是一种原地排序算法:在任何时候,数组中只有常数个元素存储在输入数组以外。要介绍堆排序首先要介绍什么是堆。
1.建堆:堆数据结构是一种数组对象,它可以被视为一颗完全二叉树,如下图。右边数组表示的堆可以用左边的完全二叉树来表示,其中若父节点对应数组下标为i,则其左孩子对应数组下标为2*i,右孩子为2*i+1。
具体代码如下:
void buildMaxHeap(int a[] , int heapSize){
for(int i = heapSize/2;i >= 1;--i)
max_heapify(a,i,heapSize); //本行代码调用max_heapify函数来保持最大堆性质,接下来会介绍到。heapSize为堆大小。
}
2.保持根堆性质:接下来介绍大根堆与小根堆的区别。大根堆某个节点的值最多是和其父节点一样大。小根堆的组织刚好相反。在堆排序中我们使用的是大根堆。为了保持大根堆的性质,我们假设从下标为i的数组元素开始,将其值与它的左孩子和右孩子相比较,找出最大值对应的下标largest,如果largest恰好与i相等,则说明节点i保持了最大堆性质。否则交换下标为largest和i所对应的数组元素值,并且对下标为largest的元素继续进行“保持根堆性质”的操作。这么说可能有些太缺乏生动性,那么一起来看看这个过程吧:
可以看到图中节点值为4的节点的保持根堆性质过程。代码如下:
void max_heapify(int a[],int i,int heapSize){
int lindex = 2*i,rindex = 2*i + 1,largest = 0;
if(lindex <= heapSize && a[lindex-1] > a[i-1])//判断左孩子和节点i的值谁大,将大值得下标保存为largest
largest = lindex;
else
largest = i;
if(rindex <= heapSize && a[rindex-1] > a[largest-1])//判断右孩子和节点largest的值谁大
largest = rindex;
if(largest != i){//如果largest不等于i
swap(a[i-1] , a[largest-1]);//交换
max_heapify(a,largest,heapSize);//继续对下标为largest、大小为heapSize的节点进行“保持根堆性质”
}
}
以下是swap函数:
void swap(int& first , int& second){
int tem = 0;
tem = first;
first = second;
second = tem;
}
3.堆排序算法:我用一个图来直观表示吧!
过程大致是将大根堆的根节点与最后一个叶子节点交换,然后缩小数组范围(即将heapSize减小),将最大值排除掉,最后对新的根节点进行“保持堆性质方法”,依次类推,代码如下:
void heapSort(int a[] , int heapSize){
buildMaxHeap(a , heapSize);//建堆
for(int i = heapSize;i >= 2;--i){
swap(a[0] , a[i-1]);//将根节点与最后的叶子节点交换
--heapSize;//缩小堆范围
max_heapify(a,1,heapSize);//保持堆性质
}
}