目录
1.了解什么是堆
堆实质上是数组,但在逻辑上可以认为是一颗完全二叉树。例如下图:
堆的还原过程中数据的下标有如下的结论:
左孩子=父亲(根)*2+1;
右孩子=父亲*2+2;
但直接还原出的堆并不能帮助我们解决问题,为此我们要了解大堆和小堆的概念。
大堆即根大于左子树同时大于右子树,小堆则同时小于。
大堆的堆顶数据即最大数据,小堆的堆顶数据即最小数据。
例如想要排出大堆需要如何做呢?
A.在左右子树已经是大堆时:
void AdjustDown(int* arr, int sz, int root)
{
int parent = root;//确定父节点位置
int child = parent * 2 + 1;//左孩子下标
while (child < sz)
{
if (arr[child] < arr[child + 1] && child < sz - 1)
{
child++;//因为要排大堆,选左右中大的。
}
if (arr[child]>arr[parent])
{
Swap(&arr[child], &arr[parent]);//交换父亲和孩子
parent = child;//赋值继续向下排序
child = parent * 2 + 1;//重新确定左孩子下标
}
else
break;//如果孩子节点比父亲结点小则该堆已经是大堆
}
}
B.因为不可能一个堆开始便符合A情况,为了处理这种情况需要从最后一个父亲往前处理:
void SortDown(int* arr, int sz)
{
int i = 0;
for (i = (sz - 2) / 2; i >= 0; i--)
{
//(sz-2)/2是最后一个孩子节点父亲的下标,从后往前处理。
AdjustDown(arr, sz, i);
}
}
通过这两步即可完成大堆的创建。
2.利用堆进行升序排序
首先要确定升序要利用大堆或是小堆,开始想必大家肯定会选择小堆,天真地认为从堆顶不断取出数据就可以完成升序排序。但仔细思考一下如果拿出堆顶的数据,剩下的数据就不容易重新排序了。对比大堆,大堆第一次可以将堆顶和最后一个位置交换位置,再从堆顶排到倒数第二的数据即可。循环往复即可升序。
看看代码和结果吧:
int main()
{
int arr[] = { 9, 4, 5, 7, 2, 8, 1, 0 };
int len = sizeof(arr) / sizeof(arr[0]);
int i = 0;
SortDown(arr, len);//先排成大堆
for (i = 0; i < len; i++)
printf("%d ", arr[i]);
printf("\n");
int end = len - 1;
while (end>0)
{
Swap(&arr[0], &arr[end]);//将最大的数放在末尾
SortDown(arr, end);//再将除去末尾数之外的数再排一次大堆
end--;
}
for (i = 0; i < len; i++)//成功排成升序
printf("%d ", arr[i]);
return 0;
}