思想:将数组调整为一个堆,保证父节点不小于两个子节点,这样根节点(堆顶)就是所有元素中的最大值。不断取出根节点并重新调整堆,直到堆被取完即可完成排序。
技巧:
1.关于“不断取出根节点形成有序数组”,可以将堆顶(最大值)与堆的最后一个节点交换,然后缩小堆(排除已取出的节点),然后下沉堆顶到正确的位置即可得到新的已调整的堆;
2.构造堆有两种方式,一种是从堆顶往后遍历所有节点,每次都将当前节点与父节点进行比较,使其上浮到正确的位置;另一种是从最后一个非叶子节点开始往前遍历直到根节点,每次都将当前节点与子节点进行比较,使其下沉到正确的位置。
由于技巧1中也用到下沉法,所以使用第二种方法会更加方便,同时由于只需要从最后一个非叶子节点进行遍历,效率也更高。
实现代码如下:
void heapSort(int num[], int start, int end)
{
//从最后一个非叶子节点开始往前调用sink调整堆
//调用完之后该节点之后的节点都是已调整好的小堆
//当根节点调用完成之后形成一个已调整好的大堆
for(int i=(start+end-1)/2;i>=start;i--)
sinkForHeapSort(num, i, start, end);//大顶堆
//直到堆的大小为1,则数组升序排序完成
for(int j=end;j>=start;)
{
swap(num[start], num[j]);//将堆顶的元素(最大值)与堆的最后一个节点交换
j--;//缩小堆
sinkForHeapSort(num, start, start, j);//重新调整堆
}
}
代码中可以对num[]数组的任意区域(start~end)进行排序,因此父节点和子节点的下标满足下式:
(childPos+start-1)/2=parentPos
sinkForHeapSort的实现代码如下:
//对pos位置的元素进行调整,使其下降到正确的位置
void sinkForHeapSort(int num[], int pos, int start, int end)
{
//(childPos+start-1)/2=parentPos
if(pos<start||start>end) return;
int childPos=pos*2-start+1;//左边子节点的坐标
while(childPos<end)//如果有左边子节点和右边子节点
{
if(num[childPos]<num[childPos+1]) childPos++;//如果右边子节点比较大,则用右边子节点与父节点进行比较
if(num[pos]>=num[childPos])break;//如果父节点比子节点都要大,则退出循环
swap(num[pos], num[childPos]);//如果父节点较小,则与较大的子节点交换
pos=childPos;//继续往下沉
childPos=pos*2-start+1;
}
//只有左边子节点时,直接与左边子节点进行比较
if(childPos==end && num[pos]<num[childPos]) swap(num[pos], num[childPos]);
}