在上一篇博客中讲解了堆,现在来讲讲基于堆实现的排序,即堆排序。
要用堆排序,首先我们需要先建堆,建堆有两种方法:向上调整法建堆和向下调整法建堆。接下来我们分析这两种方法的时间复杂度。
向上调整法:
向下调整法:
根据上面的分析,我们得到:向下调整法建堆更高效,所以接下来的堆排序实现过程中我们用向下调整法建堆。向下调整法建堆需要从第一个非叶子结点开始,然后调整完后调整上边的(即从下往上),如果从下标为0的结点开始向下调整会有问题,因为我们调整时是交换孩子和父亲,而当其深度大于2时,并且在后边(隔了几层)有更小的数时,交换后是无法保证最小的数在堆顶的,因为这时候是按照从上往下的顺序进行向下调整,在下边的更小的数是不能再往上交换的(除了其父父亲)。
接下来我们分析实现堆排序的过程。
排序有升序和降序,而堆中有大堆和小堆,我们应该建大堆还是小堆呢?答案是升序建大堆,降序建小堆。
我们知道,大堆中堆顶元素是所有元素中最大的那个,小堆中堆顶元素是所有元素中最小的那个,堆排序便是依次来实现的。以降序为例,给定一个有n个元素的数组,当我们建好堆后,我们不断把堆顶元素与最后一个元素交换,这样最后一个元素就放好了,然后重新调整为小堆,在下一次交换时,针对的就是前n-1个数据了,然后一直这样交换下去,这便是堆排序的思想。
接下来上代码:
//交换数据
void swap(HPDataType* x, HPDataType* y)
{
HPDataType t = 0;
t = *x;
*x = *y;
*y = t;
}
//向下调整
void AdjustDown(HPDataType* a, int size, int parents)
{
int child = parents * 2 + 1;//左孩子
while (child < size)
{
//如果右孩子更小,交换
//注意child+1<size这个条件,实际最多只能访问到size-1的数据
if (child + 1 < size && a[child] > a[child + 1])
{
child++;
}
if (a[child] < a[parents])
{
swap(&a[child], &a[parents]);
parents = child;
child = parents * 2 + 1;
}
else
{
break;
}
}
}
//堆排序
void HeapSort(HPDataType* a, int n)
{
//数组建堆(向下调整)
int i = 0;
//(n-1-1)/2是第一个非叶子结点
for (i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
//最后一个数下标
int end = n - 1;
while (end > 0)
{
swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
//注意调整和自减顺序,end是最后一个数的下标,交换完后,前边的数的个数即end
}
}
int main()
{
int arr[10] = { 1,8,6,4,2,7,3,5,9,0 };
HeapSort(arr, 10);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
测试结果: