目录
堆排序是一种时间复杂度较低的排序算法,相较于冒泡排序的时间复杂度O(n^2),堆排序的时间复杂度只有O(n*logN),和快速排序的时间复杂度相同。
堆排序的实现:
建堆:
要实现堆排序首先要建堆,堆分为两种类型,一种是大根堆,一种是小根堆;
可以辅助理解为:大根堆的根节点的值为数值序列中最大值,小根堆的根节点的值为数值序列中最小值。
建队代码:
下面是大根堆的函数实现代码,如果要建小根堆,只要将AdjustUp函数中的if语句从if (a[child] > a[parent])改为if (a[child] < a[parent]);
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#define HeapType int
typedef struct Heap
{
HeapType* a;
int size;
int capacity;
}Heap;
void HeapInit(Heap* hp)//堆的初始化
{
assert(hp);
hp->a = NULL;
hp->size = 0;
hp->capacity = 0;
}
void HeapDestory(Heap* hp)//堆的销毁
{
assert(hp);
hp->size = 0;
hp->capacity = 0;
free(hp->a);
hp->a = NULL;
}
void Swap(HeapType* p1, HeapType* p2)
{
HeapType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void AdjustUp(HeapType* a, int child)
//判断函数,如果子节点的值大于父节点的值
//交换两个节点的值
{
assert(a);
while (child > 0)
{
int parent = (child - 1) / 2;
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapPush(Heap* hp, HeapType x)
//插入节点函数,插入前判断空间是否足够
//足够则插入,不够则扩容
{
assert(hp);
if (hp->size == hp->capacity)
{
int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;
HeapType* tmp = (HeapType*)realloc(hp->a, sizeof(hp->a) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
hp->a = tmp;
hp->capacity = newcapacity;
}
hp->a[hp->size] = x;
hp->size++;
AdjustUp(hp->a, hp->size - 1);
}
int HeapEmpty(Heap* hp)
{
assert(hp);
return hp->size == 0;
}
void AdjustDown(HeapType* a, int size, int parent)
{
int child = parent * 2 + 1;//默认找到右孩子
while (child<size)
{
if (child + 1 < size && a[child + 1] > a[child])//找到两个孩子中值较大的一个
{
child++;
}
if (a[child] > a[parent])//交换位置
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapPop(Heap* hp)
{
assert(hp);
assert(!HeapEmpty(hp));
Swap(&hp->a[0], &hp->a[hp->size - 1]);
hp->size--;
AdjustDown(hp->a, hp->size, 0);
}
HeapType HeapTop(Heap* hp)
{
assert(hp);
assert(!(HeapEmpty(hp)));
return hp->a[0];
}
int HeapSize(Heap* hp)
{
assert(hp);
return hp->size;
}
建堆时只要在主函数中调用以下函数建堆即可。这边给出一个简单的范例:
int main()
{
Heap hp;
HeapInit(&hp);
HeapType arr[] = { 23,53,143,42,4,445,322 };
for (int i = 0; i < sizeof(arr) / sizeof(HeapType); i++)
{
HeapPush(&hp, arr[i]);
}
return 0;
}
具体函数的解析可以看我上一篇文章:C语言基础数据结构——树,二叉树,堆的实现_S+叮当猫的博客-CSDN博客
堆排序的第一种实现:时间复杂度为O(N*logN)
解析:
①创建结构体,存放数组的信息,并初始化(结构体中存储元素的空间和原数组的空间不为同一块空间,是动态开辟的)
②循环使用HeapPush函数插入数组中的数据,使得数组变为堆
③之后使用HeapTop函数取得堆首元素,并将堆首元素(当前为大根堆,所以堆首元素为数组中最大值)复制到原数组,然后执行HeapPop函数删除堆首元素,并重新成堆(HeapPop函数中调用了向下调整函数)。
④循环获取堆首元素,完成排序。
void HeapSort(HeapType* a, int size)
{
Heap hp;
HeapInit(&hp);
int i = 0;
for (i = 0; i < size; i++)
{
HeapPush(&hp, a[i]);
}
i = 0;
while (!HeapEmpty(&hp))
{
int top = HeapTop(&hp);
a[i++] = top;
HeapPop(&hp);
}
HeapDestory(&hp);
}
缺点:
这种方法在排序前,首先要动态开辟空间,不是在原数组的操作,就会造成空间的浪费;
其次,要在结构体的空间和原数组之间来回拷贝数据,比较麻烦;
堆排序的第二种实现:时间复杂度为O(N*logN)
第二种方法的实现有一点局限,但是会在原空间操作数组使之直接成堆,减少了空间的消耗。
降序——建小堆
升序——建大堆
void HeapSort2(HeapType* a, int size)
{
assert(a);
int i = 0;
for (i = 1; i < size; i++)
{
AdjustUp(a, i);
}
i = 0;
int end = size - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);//交换堆首元素和堆尾元素,建立大堆
AdjustDown(a, end, 0);//向下调整,重新成堆
end--;
}
}
解析(升序——建大堆的情况):
①因为一个元素也可以看作是一个堆,所以我们将数组的第一个元素当作一个堆,向其中循环插入元素,使之成为一个大堆。
②因为建堆完成之后,数组为大堆,所以数组第一个元素为整个数组中最大值;
这时我们将第一个元素与最后一个元素互换,完成交换后当前数组的最后一个元素不在参与调整;
然后调用向下调整函数,调整数组首元素;
完成后数组首元素再次变为数组中除最后一个元素外最大的元素。
依次循环,直到当前数组只剩一个值时结束循环;
此时数组变为升序数组。
降序——建小堆方法实现:
只需将AdjustUp函数的 “>” 改为 “<”先建出小堆;
然后将AdjustDown函数中 “if (child + 1 < size && a[child + 1] > a[child])” 改为 “if (child + 1 > size && a[child + 1] < a[child])”;
最后将AdjustDown函数中 “if (a[child] > a[parent])” 改为 “if (a[child] < a[parent])”即可。
堆排序的第三种实现:
在第二种方法的基础上优化建堆。
从原来的向上调整建堆改变为向下调整建堆,使得原来建堆的时间复杂度从O(N*logN)变为O(N);
解析:
向下调整建堆不需要对8~15节点调整,因为单个节点可以看作是一个堆,所以只需要从第一个非叶子节点开始向下调整,也就是最后一个节点的父亲节点开始向下调整(7号节点开始)。
我们从7号节点开始向下调整,这样7,14,15这三个节点就成堆;
然后开始调整6号节点,使得6,12,13这三个节点成堆,
再调整5号节点,使得5,10,11这三个节点成堆。
依此类推,直到1号节点向下调整完成,就完成了建堆。
这种建堆方式优于向上调整建堆。
void HeapSort3(HeapType* a, int size)
{
assert(a);
int i = 0;
for (i = (size - 2) / 2; i >= 0; i--)
{
AdjustDown(a, size, i);//向下调整建堆
}
i = 0;
int end = size - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
}
}